diff --git a/packages/main/src/RangeSlider.hbs b/packages/main/src/RangeSlider.hbs
index 867637360b0a..6ac3e9b8e866 100644
--- a/packages/main/src/RangeSlider.hbs
+++ b/packages/main/src/RangeSlider.hbs
@@ -1,8 +1,8 @@
{{>include "./SliderBase.hbs"}}
{{#*inline "handlesAriaText"}}
- {{_ariaHandlesText.startHandleText}}
- {{_ariaHandlesText.endHandleText}}
+ {{_ariaHandlesText.startHandleText}}
+ {{_ariaHandlesText.endHandleText}}
{{/inline}}
{{#*inline "progressBar"}}
@@ -22,56 +22,88 @@
aria-valuemax="{{max}}"
aria-valuenow="{{_ariaValueNow}}"
aria-valuetext="From {{startValue}} to {{endValue}}"
- aria-labelledby="{{_ariaLabelledByProgressBarRefs}}"
+ aria-labelledby="ui5-slider-sliderDesc"
aria-disabled="{{_ariaDisabled}}"
>
{{/inline}}
{{#*inline "handles"}}
-
-
+
+
+
+
+
{{#if showTooltip}}
-
{{/if}}
-
-
-
+
+
+
+
{{#if showTooltip}}
-
{{/if}}
{{/inline}}
diff --git a/packages/main/src/RangeSlider.ts b/packages/main/src/RangeSlider.ts
index 2a877b2da726..3ea110070748 100644
--- a/packages/main/src/RangeSlider.ts
+++ b/packages/main/src/RangeSlider.ts
@@ -5,18 +5,22 @@ import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import type { IFormInputElement } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js";
import {
isEscape,
+ isEnter,
isHome,
isEnd,
} from "@ui5/webcomponents-base/dist/Keys.js";
import SliderBase from "./SliderBase.js";
import Icon from "./Icon.js";
import RangeSliderTemplate from "./generated/templates/RangeSliderTemplate.lit.js";
+import Input from "./Input.js";
// Texts
import {
RANGE_SLIDER_ARIA_DESCRIPTION,
RANGE_SLIDER_START_HANDLE_DESCRIPTION,
RANGE_SLIDER_END_HANDLE_DESCRIPTION,
+ SLIDER_TOOLTIP_INPUT_LABEL,
+ SLIDER_TOOLTIP_INPUT_DESCRIPTION,
} from "./generated/i18n/i18n-defaults.js";
// Styles
@@ -88,7 +92,7 @@ type AffectedValue = "startValue" | "endValue";
languageAware: true,
formAssociated: true,
template: RangeSliderTemplate,
- dependencies: [Icon],
+ dependencies: [Icon, Input],
styles: [SliderBase.styles, rangeSliderStyles],
})
class RangeSlider extends SliderBase implements IFormInputElement {
@@ -115,6 +119,12 @@ class RangeSlider extends SliderBase implements IFormInputElement {
@property({ type: Boolean })
rangePressed = false;
+ @property({ type: Boolean })
+ _isStartValueValid = false;
+
+ @property({ type: Boolean })
+ _isEndValueValid = false;
+
_startValueInitial?: number;
_endValueInitial?: number;
_valueAffected?: AffectedValue;
@@ -128,6 +138,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
_secondHandlePositionFromStart?: number;
_selectedRange?: number;
_reversedValues = false;
+ _lastValidStartValue: string;
+ _lastValidEndValue: string;
+ _areInputValuesSwapped = false;
@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;
@@ -149,6 +162,8 @@ class RangeSlider extends SliderBase implements IFormInputElement {
super();
this._stateStorage.startValue = undefined;
this._stateStorage.endValue = undefined;
+ this._lastValidStartValue = this.min.toString();
+ this._lastValidEndValue = this.max.toString();
}
get tooltipStartValue() {
@@ -210,6 +225,10 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this.update(affectedValue, this.startValue, this.endValue);
}
+ if (this.editableTooltip) {
+ this._saveInputValues();
+ }
+
if (!this.isCurrentStateOutdated()) {
return;
}
@@ -217,6 +236,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this.notResized = true;
this.syncUIAndState();
this._updateHandlesAndRange(0);
+ this.update(this._valueAffected, this.startValue, this.endValue);
}
syncUIAndState() {
@@ -279,7 +299,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
* Resets the stored Range Slider's initial values saved when it was first focused
* @private
*/
- _onfocusout() {
+ _onfocusout(e: FocusEvent) {
if (this._isFocusing()) {
this._preventFocusOut();
return;
@@ -289,20 +309,56 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this._startValueInitial = undefined;
this._endValueInitial = undefined;
- if (this.showTooltip) {
+ if (this.showTooltip && !(e.relatedTarget as HTMLInputElement)?.hasAttribute("ui5-input")) {
this._tooltipVisibility = SliderBase.TOOLTIP_VISIBILITY.HIDDEN;
}
}
+ _onInputFocusOut(e: FocusEvent) {
+ const tooltipInput = e.target as Input;
+ const oppositeTooltipInput: Input = tooltipInput.hasAttribute("data-sap-ui-start-value") ? this.shadowRoot!.querySelector("ui5-input[data-sap-ui-end-value]")! : this.shadowRoot!.querySelector("ui5-input[data-sap-ui-start-value]")!;
+ const relatedTarget = e.relatedTarget as HTMLElement;
+
+ if (this.startValue > this.endValue) {
+ this._areInputValuesSwapped = true;
+ oppositeTooltipInput.focus();
+ return;
+ }
+
+ if (tooltipInput.hasAttribute("data-sap-ui-start-value")) {
+ this._setAffectedValue("startValue");
+ } else {
+ this._setAffectedValue("endValue");
+ }
+
+ if (!this._areInputValuesSwapped || !this.shadowRoot!.contains(relatedTarget)) {
+ this._tooltipVisibility = SliderBase.TOOLTIP_VISIBILITY.HIDDEN;
+ }
+
+ this._updateValueFromInput(e);
+ this._updateInputValue();
+ this.update(this._valueAffected, parseFloat(this._lastValidStartValue), parseFloat(this._lastValidEndValue));
+
+ const isTooltipInputValueValid = parseFloat(tooltipInput.value) >= this.min && parseFloat(tooltipInput.value) <= this.max;
+
+ if (!isTooltipInputValueValid) {
+ tooltipInput.value = tooltipInput.hasAttribute("data-sap-ui-start-value") ? this._lastValidStartValue : this._lastValidEndValue;
+ tooltipInput.valueState = "None";
+ }
+ }
+
/**
* Handles keyup logic. If one of the handles came across the other
* swap the start and end values. Reset the affected value by the finished
* user interaction.
* @private
*/
- _onkeyup() {
- super._onkeyup();
- this._setAffectedValue(undefined);
+ _onkeyup(e: KeyboardEvent) {
+ super._onKeyupBase();
+
+ if (!isEnter(e)) {
+ this._setAffectedValue(undefined);
+ }
if (this.startValue !== this._startValueAtBeginningOfAction || this.endValue !== this._endValueAtBeginningOfAction) {
this.fireEvent("change");
@@ -429,7 +485,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
_onmousedown(e: TouchEvent | MouseEvent) {
// 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) {
+ if (this.disabled || this._effectiveStep === 0 || (e.target as HTMLElement).hasAttribute("ui5-input")) {
return;
}
@@ -484,7 +540,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
e.preventDefault();
// If 'step' is 0 no interaction is available as there is no constant quantitative representation of the value
- if (this.disabled || this._effectiveStep === 0) {
+ if (this.disabled || this._effectiveStep === 0 || (e.target as HTMLElement).hasAttribute("ui5-input")) {
return;
}
@@ -525,7 +581,11 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this.update(undefined, newValues[0], newValues[1]);
}
- _handleUp() {
+ _handleUp(e: MouseEvent) {
+ if ((e.target as HTMLElement).hasAttribute("ui5-input")) {
+ return;
+ }
+
this._setAffectedValueByFocusedElement();
this._setAffectedValue(undefined);
@@ -541,6 +601,31 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this._endValueAtBeginningOfAction = undefined;
}
+ _updateValueFromInput(e: Event) {
+ if (this._areInputValuesSwapped) {
+ return;
+ }
+
+ const input = e.target as HTMLInputElement;
+ const inputValue = parseFloat(input.value);
+ const isValueValid = inputValue >= this._effectiveMin && inputValue <= this._effectiveMax;
+
+ if (!isValueValid) {
+ return;
+ }
+
+ if (input.hasAttribute("data-sap-ui-start-value")) {
+ this.startValue = inputValue;
+ return;
+ }
+
+ this.endValue = inputValue;
+
+ if (this.startValue > this.endValue) {
+ this._areInputValuesSwapped = true;
+ }
+ }
+
/**
* Determines where the press occured and which values of the Range Slider
* handles should be updated on further interaction.
@@ -635,6 +720,10 @@ class RangeSlider extends SliderBase implements IFormInputElement {
* @protected
*/
focusInnerElement() {
+ if (this.editableTooltip && this._tooltipVisibility === SliderBase.TOOLTIP_VISIBILITY.HIDDEN) {
+ return;
+ }
+
const isReversed = this._areValuesReversed();
const affectedValue = this._valueAffected;
@@ -750,6 +839,111 @@ class RangeSlider extends SliderBase implements IFormInputElement {
}
}
+ _onInputKeydown(e: KeyboardEvent): void {
+ const targetedInput = e.target as Input;
+ const startValueInput = this.shadowRoot!.querySelector("ui5-input[data-sap-ui-start-value]") as Input;
+ const endValueInput = this.shadowRoot!.querySelector("ui5-input[data-sap-ui-end-value]") as Input;
+
+ const startValue = parseFloat(startValueInput.value);
+ const endValue = parseFloat(endValueInput.value);
+ const affectedValue = targetedInput.hasAttribute("data-sap-ui-start-value") ? "startValue" : "endValue";
+
+ super._onInputKeydown(e);
+
+ if (isEnter(e) && startValue > endValue) {
+ const swappedInput = affectedValue === "startValue" ? endValueInput : startValueInput;
+ const isValueValid = parseFloat(targetedInput.value) >= this.min && parseFloat(startValueInput.value) <= this.max;
+
+ if (!isValueValid) {
+ targetedInput.valueState = "Negative";
+ return;
+ }
+
+ this._isEndValueValid = parseFloat(endValueInput.value) >= this.min && parseFloat(endValueInput.value) <= this.max;
+
+ this._areInputValuesSwapped = true;
+ this._setAffectedValue(affectedValue === "startValue" ? "endValue" : "startValue");
+
+ startValueInput.value = this._getFormattedValue(this.endValue.toString());
+ endValueInput.value = this._getFormattedValue(this.startValue.toString());
+ swappedInput.focus();
+
+ return;
+ }
+
+ this._setAffectedValue(affectedValue);
+ }
+
+ _updateInputValue() {
+ const startValueInput = this.shadowRoot!.querySelector("ui5-input[data-sap-ui-start-value]") as Input;
+ const endValueInput = this.shadowRoot!.querySelector("ui5-input[data-sap-ui-end-value]") as Input;
+
+ if (!startValueInput && !endValueInput) {
+ return;
+ }
+
+ this._isStartValueValid = parseFloat(startValueInput.value) >= this.min && parseFloat(startValueInput.value) <= this.max;
+ this._isEndValueValid = parseFloat(endValueInput.value) >= this.min && parseFloat(endValueInput.value) <= this.max;
+
+ if (!this._isStartValueValid) {
+ startValueInput.valueState = "Negative";
+ return;
+ }
+
+ if (!this._isEndValueValid) {
+ endValueInput.valueState = "Negative";
+ return;
+ }
+
+ this._lastValidStartValue = startValueInput.value;
+ this._lastValidEndValue = endValueInput.value;
+
+ startValueInput.valueState = "None";
+ endValueInput.valueState = "None";
+ }
+
+ _saveInputValues() {
+ const startValueInput = this.shadowRoot!.querySelector("ui5-input[data-sap-ui-start-value]") as Input;
+ const endValueInput = this.shadowRoot!.querySelector("ui5-input[data-sap-ui-end-value]") as Input;
+
+ if (this.editableTooltip && startValueInput && endValueInput) {
+ const inputStartValue = parseFloat(startValueInput.value);
+ const inputEndValue = parseFloat(endValueInput.value);
+
+ const isStartValueValid = inputStartValue >= this.min && inputStartValue <= this.max;
+ const isEndValueValid = inputEndValue >= this.min && inputEndValue <= this.max;
+
+ if (this._isUserInteraction) {
+ startValueInput.value = isStartValueValid ? this._getFormattedValue(this.startValue.toString()) : this._getFormattedValue(this._lastValidStartValue);
+ endValueInput.value = isEndValueValid ? this._getFormattedValue(this.endValue.toString()) : this._getFormattedValue(this._lastValidEndValue);
+
+ this.startValue = parseFloat(this._getFormattedValue(this.startValue.toString()));
+ this.endValue = parseFloat(this._getFormattedValue(this.endValue.toString()));
+
+ this.syncUIAndState();
+ this._updateHandlesAndRange(0);
+ this.update(this._valueAffected, this.startValue, this.endValue);
+ return;
+ }
+
+ this._lastValidStartValue = isStartValueValid ? this._getFormattedValue(inputStartValue.toString()) : this._getFormattedValue(this._lastValidStartValue);
+ this._lastValidEndValue = isEndValueValid ? this._getFormattedValue(inputEndValue.toString()) : this._getFormattedValue(this._lastValidEndValue);
+
+ if (startValueInput.valueState !== "Negative" && endValueInput.valueState !== "Negative") {
+ startValueInput.value = isStartValueValid ? this._getFormattedValue(inputStartValue.toString()) : this._getFormattedValue(this._lastValidStartValue);
+ endValueInput.value = isEndValueValid ? this._getFormattedValue(inputEndValue.toString()) : this._getFormattedValue(this._lastValidEndValue);
+ }
+ }
+ }
+
+ _getFormattedValue(value: string) {
+ const valueNumber = parseFloat(value);
+ const ctor = this.constructor as typeof RangeSlider;
+ const stepPrecision = ctor._getDecimalPrecisionOfNumber(this._effectiveStep);
+
+ return valueNumber.toFixed(stepPrecision).toString();
+ }
+
/**
* Swaps the start and end values of the handles if one came accros the other:
* - If the start value is greater than the endValue swap them and their handles
@@ -781,8 +975,13 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this._setValuesAreReversed();
this._updateHandlesAndRange(this[affectedValue]);
- this.focusInnerElement();
+
+ if (!this._areInputValuesSwapped) {
+ this.focusInnerElement();
+ }
+
this.syncUIAndState();
+ this._areInputValuesSwapped = false;
}
/**
@@ -830,16 +1029,12 @@ class RangeSlider extends SliderBase implements IFormInputElement {
return this.shadowRoot!.querySelector
(".ui5-slider-progress")!;
}
- get _ariaLabelledByStartHandleRefs() {
- return [`${this._id}-accName`, `${this._id}-startHandleDesc`].join(" ").trim();
- }
-
- get _ariaLabelledByEndHandleRefs() {
- return [`${this._id}-accName`, `${this._id}-endHandleDesc`].join(" ").trim();
+ get _ariaLabelledByInputText() {
+ return RangeSlider.i18nBundle.getText(SLIDER_TOOLTIP_INPUT_LABEL);
}
- get _ariaLabelledByProgressBarRefs() {
- return [`${this._id}-accName`, `${this._id}-sliderDesc`].join(" ").trim();
+ get _ariaDescribedByInputText() {
+ return RangeSlider.i18nBundle.getText(SLIDER_TOOLTIP_INPUT_DESCRIPTION);
}
get styles() {
diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs
index 00e0940882b3..21bb8cf6c1fe 100644
--- a/packages/main/src/Slider.hbs
+++ b/packages/main/src/Slider.hbs
@@ -17,26 +17,45 @@
{{/inline}}
{{#*inline "handles"}}
-
-
+
+
+
+
{{#if showTooltip}}
-
{{/if}}
{{/inline}}
diff --git a/packages/main/src/Slider.ts b/packages/main/src/Slider.ts
index 699eb71d7f2e..743c3aa7e3e6 100644
--- a/packages/main/src/Slider.ts
+++ b/packages/main/src/Slider.ts
@@ -6,6 +6,7 @@ import { isEscape } from "@ui5/webcomponents-base/dist/Keys.js";
import type { IFormInputElement } from "@ui5/webcomponents-base/dist/features/InputElementsFormSupport.js";
import SliderBase from "./SliderBase.js";
import Icon from "./Icon.js";
+import Input from "./Input.js";
// Template
import SliderTemplate from "./generated/templates/SliderTemplate.lit.js";
@@ -13,6 +14,8 @@ import SliderTemplate from "./generated/templates/SliderTemplate.lit.js";
// Texts
import {
SLIDER_ARIA_DESCRIPTION,
+ SLIDER_TOOLTIP_INPUT_DESCRIPTION,
+ SLIDER_TOOLTIP_INPUT_LABEL,
} from "./generated/i18n/i18n-defaults.js";
/**
@@ -74,7 +77,7 @@ import {
languageAware: true,
formAssociated: true,
template: SliderTemplate,
- dependencies: [Icon],
+ dependencies: [Icon, Input],
})
class Slider extends SliderBase implements IFormInputElement {
/**
@@ -91,6 +94,9 @@ class Slider extends SliderBase implements IFormInputElement {
_valueOnInteractionStart?: number;
_progressPercentage = 0;
_handlePositionFromStart = 0;
+ _lastValidInputValue: string;
+ _tooltipInputValue: string = this.value.toString();
+ _tooltipInputValueState: string = "None";
get formFormattedValue() {
return this.value.toString();
@@ -102,6 +108,7 @@ class Slider extends SliderBase implements IFormInputElement {
constructor() {
super();
this._stateStorage.value = undefined;
+ this._lastValidInputValue = this.min.toString();
}
/**
@@ -116,6 +123,10 @@ class Slider extends SliderBase implements IFormInputElement {
*
*/
onBeforeRendering() {
+ if (this.editableTooltip) {
+ this._updateInputValue();
+ }
+
if (!this.isCurrentStateOutdated()) {
return;
}
@@ -162,7 +173,7 @@ class Slider extends SliderBase implements IFormInputElement {
_onmousedown(e: TouchEvent | MouseEvent) {
// 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.step === 0 || (e.target as HTMLElement).hasAttribute("ui5-input")) {
return;
}
@@ -196,7 +207,7 @@ class Slider extends SliderBase implements IFormInputElement {
}
}
- _onfocusout() {
+ _onfocusout(e: FocusEvent) {
// Prevent focusout when the focus is getting set within the slider internal
// element (on the handle), before the Slider' customElement itself is finished focusing
if (this._isFocusing()) {
@@ -208,7 +219,7 @@ class Slider extends SliderBase implements IFormInputElement {
// value that was saved when it was first focused in
this._valueInitial = undefined;
- if (this.showTooltip) {
+ if (this.showTooltip && !(e.relatedTarget as HTMLInputElement)?.hasAttribute("ui5-input")) {
this._tooltipVisibility = SliderBase.TOOLTIP_VISIBILITY.HIDDEN;
}
}
@@ -218,6 +229,10 @@ class Slider extends SliderBase implements IFormInputElement {
* @private
*/
_handleMove(e: TouchEvent | MouseEvent) {
+ if ((e.target as HTMLElement).hasAttribute("ui5-input")) {
+ return;
+ }
+
e.preventDefault();
// If step is 0 no interaction is available because there is no constant
@@ -237,7 +252,11 @@ class Slider extends SliderBase implements IFormInputElement {
/** Called when the user finish interacting with the slider
* @private
*/
- _handleUp() {
+ _handleUp(e: TouchEvent | MouseEvent) {
+ if ((e.target as HTMLElement).hasAttribute("ui5-input")) {
+ return;
+ }
+
if (this._valueOnInteractionStart !== this.value) {
this.fireEvent("change");
}
@@ -246,6 +265,41 @@ class Slider extends SliderBase implements IFormInputElement {
this._valueOnInteractionStart = undefined;
}
+ _onInputFocusOut(e: FocusEvent) {
+ const tooltipInput = this.shadowRoot!.querySelector("ui5-input") as Input;
+
+ this._tooltipVisibility = SliderBase.TOOLTIP_VISIBILITY.HIDDEN;
+ this._updateValueFromInput(e);
+
+ if (!this._isInputValueValid) {
+ tooltipInput.value = this._lastValidInputValue;
+ this._isInputValueValid = true;
+ this._tooltipInputValueState = "None";
+ }
+ }
+
+ _updateInputValue() {
+ const tooltipInput = this.shadowRoot!.querySelector("ui5-input") as Input;
+
+ if (!tooltipInput) {
+ return;
+ }
+
+ this._isInputValueValid = parseFloat(tooltipInput.value) >= this.min && parseFloat(tooltipInput.value) <= this.max;
+
+ if (!this._isInputValueValid) {
+ this._tooltipInputValue = this._lastValidInputValue;
+ this._isInputValueValid = true;
+ this._tooltipInputValueState = "Negative";
+
+ return;
+ }
+
+ this._tooltipInputValue = this.value.toString();
+ this._lastValidInputValue = this._tooltipInputValue;
+ this._tooltipInputValueState = "None";
+ }
+
/** Determines if the press is over the handle
* @private
*/
@@ -281,6 +335,10 @@ class Slider extends SliderBase implements IFormInputElement {
}
}
+ get inputValue() {
+ return this.value.toString();
+ }
+
get styles() {
return {
progress: {
@@ -321,6 +379,14 @@ class Slider extends SliderBase implements IFormInputElement {
return Slider.i18nBundle.getText(SLIDER_ARIA_DESCRIPTION);
}
+ get _ariaDescribedByInputText() {
+ return Slider.i18nBundle.getText(SLIDER_TOOLTIP_INPUT_DESCRIPTION);
+ }
+
+ get _ariaLabelledByInputText() {
+ return Slider.i18nBundle.getText(SLIDER_TOOLTIP_INPUT_LABEL);
+ }
+
get tickmarksObject() {
const count = this._tickmarksCount;
const arr = [];
diff --git a/packages/main/src/SliderBase.hbs b/packages/main/src/SliderBase.hbs
index f4795d150338..da75fbd468a0 100644
--- a/packages/main/src/SliderBase.hbs
+++ b/packages/main/src/SliderBase.hbs
@@ -5,7 +5,7 @@
@mouseover="{{_onmouseover}}"
@mouseout="{{_onmouseout}}"
@keydown="{{_onkeydown}}"
- @keyup="{{_onkeyup}}"
+ @keyup="{{_onKeyupBase}}"
part="root-container"
>
{{> handlesAriaText}}
@@ -37,11 +37,20 @@
{{> handles}}
- {{accessibleName}}
- {{_ariaLabelledByText}}
+ {{#if accessibleName}}
+ {{accessibleName}}
+ {{/if}}
+
+ {{_ariaLabelledByText}}
+
+ {{#if editableTooltip}}
+ {{_ariaDescribedByInputText}}
+ {{_ariaLabelledByInputText}}
+ {{/if}}
+
{{#*inline "handlesAriaText"}}{{/inline}}
{{#*inline "progressBar"}}{{/inline}}
-{{#*inline "handles"}}{{/inline}}
+{{#*inline "handles"}}{{/inline}}
\ No newline at end of file
diff --git a/packages/main/src/SliderBase.ts b/packages/main/src/SliderBase.ts
index c401cc6c2ae1..2dce681bc11b 100644
--- a/packages/main/src/SliderBase.ts
+++ b/packages/main/src/SliderBase.ts
@@ -8,7 +8,8 @@ import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delega
import type { PassiveEventListenerObject } from "@ui5/webcomponents-base/dist/types.js";
import "@ui5/webcomponents-icons/dist/direction-arrows.js";
import {
- isEscape, isHome, isEnd, isUp, isDown, isRight, isLeft, isUpCtrl, isDownCtrl, isRightCtrl, isLeftCtrl, isPlus, isMinus, isPageUp, isPageDown,
+ isEscape, isHome, isEnd, isUp, isDown, isRight, isLeft, isUpCtrl, isDownCtrl, isRightCtrl, isLeftCtrl, isPlus, isMinus, isPageUp, isPageDown, isF2,
+ isEnter,
} from "@ui5/webcomponents-base/dist/Keys.js";
// Styles
@@ -108,6 +109,18 @@ abstract class SliderBase extends UI5Element {
@property({ type: Boolean })
showTooltip = false;
+ /**
+ *
+ * Indicates whether input fields should be used as tooltips for the handles.
+ *
+ * **Note:** Setting this option to true will only work if showTooltip is set to true.
+ * **Note:** In order for the component to comply with the accessibility standard, it is recommended to set the editableTooltip property to true.
+ * @default false
+ * @public
+ */
+ @property({ type: Boolean })
+ editableTooltip = false;
+
/**
* Defines whether the slider is in disabled state.
* @default false
@@ -125,6 +138,12 @@ abstract class SliderBase extends UI5Element {
@property()
accessibleName?: string;
+ /**
+ * @private
+ */
+ @property({ type: Number })
+ value = 0;
+
/**
* @private
*/
@@ -137,9 +156,12 @@ abstract class SliderBase extends UI5Element {
@property({ type: Boolean })
_hiddenTickmarks = false;
+ @property({ type: Boolean })
+ _isInputValueValid = false;
+
_resizeHandler: ResizeObserverCallback;
_moveHandler: (e: TouchEvent | MouseEvent) => void;
- _upHandler: () => void;
+ _upHandler: (e: TouchEvent | MouseEvent) => void;
_stateStorage: StateStorage;
_ontouchstart: PassiveEventListenerObject;
notResized = false;
@@ -150,6 +172,7 @@ abstract class SliderBase extends UI5Element {
_oldMax?: number;
_labelWidth = 0;
_labelValues?: Array
;
+ _valueOnInteractionStart?: number;
async formElementAnchor() {
return this.getFocusDomRefAsync();
@@ -180,12 +203,14 @@ abstract class SliderBase extends UI5Element {
_handleMove(e: TouchEvent | MouseEvent) {} // eslint-disable-line
- _handleUp() {}
+ _handleUp(e: TouchEvent | MouseEvent) {} // eslint-disable-line
_onmousedown(e: TouchEvent | MouseEvent) {} // eslint-disable-line
_handleActionKeyPress(e: Event) {} // eslint-disable-line
+ _updateInputValue() {}
+
// used in base template, but implemented in subclasses
abstract styles: {
label: object,
@@ -281,11 +306,17 @@ abstract class SliderBase extends UI5Element {
}
_onkeydown(e: KeyboardEvent) {
- if (this.disabled || this._effectiveStep === 0) {
+ const target = e.target as HTMLElement;
+
+ if (isF2(e) && target.classList.contains("ui5-slider-handle")) {
+ (target.parentNode!.querySelector(".ui5-slider-handle-container ui5-input") as HTMLElement).focus();
+ }
+
+ if (this.disabled || this._effectiveStep === 0 || target.hasAttribute("ui5-slider-handle")) {
return;
}
- if (SliderBase._isActionKey(e)) {
+ if (SliderBase._isActionKey(e) && target && !target.hasAttribute("ui5-input")) {
e.preventDefault();
this._isUserInteraction = true;
@@ -293,7 +324,42 @@ abstract class SliderBase extends UI5Element {
}
}
- _onkeyup() {
+ _onInputKeydown(e: KeyboardEvent) {
+ const target = e.target as HTMLElement;
+
+ if (isF2(e) && target.hasAttribute("ui5-input")) {
+ (target.parentNode!.parentNode!.querySelector(".ui5-slider-handle") as HTMLElement).focus();
+ }
+
+ if (isEnter(e)) {
+ this._updateInputValue();
+ this._updateValueFromInput(e);
+ }
+ }
+
+ _onInputChange() {
+ if (this._valueOnInteractionStart !== this.value) {
+ this.fireEvent("change");
+ }
+ }
+
+ _onInputInput() {
+ this.fireEvent("input");
+ }
+
+ _updateValueFromInput(e: Event) {
+ const input = e.target as HTMLInputElement;
+ const value = parseFloat(input.value);
+ this._isInputValueValid = value >= this._effectiveMin && value <= this._effectiveMax;
+
+ if (!this._isInputValueValid) {
+ return;
+ }
+
+ this.value = value;
+ }
+
+ _onKeyupBase() {
if (this.disabled) {
return;
}
@@ -403,9 +469,10 @@ abstract class SliderBase extends UI5Element {
* @private
*/
_handleFocusOnMouseDown(e: TouchEvent | MouseEvent) {
- const focusedElement = this.shadowRoot!.activeElement;
+ const currentlyFocusedElement = this.shadowRoot!.activeElement;
+ const elementToBeFocused = e.target as HTMLElement;
- if (!focusedElement || focusedElement !== e.target) {
+ if ((!currentlyFocusedElement || currentlyFocusedElement !== elementToBeFocused) && !elementToBeFocused.hasAttribute("ui5-input")) {
this._preserveFocus(true);
this.focusInnerElement();
}
@@ -725,8 +792,20 @@ abstract class SliderBase extends UI5Element {
return this.disabled ? "-1" : "0";
}
- get _ariaLabelledByHandleRefs() {
- return [`${this._id}-accName`, `${this._id}-sliderDesc`].join(" ").trim();
+ get _ariaDescribedByHandleText() {
+ return this.editableTooltip ? "ui5-slider-accName ui5-slider-InputDesc" : undefined;
+ }
+
+ get _ariaLabelledByHandleText() {
+ return this.accessibleName ? "ui5-slider-accName" : undefined;
+ }
+
+ get _ariaDescribedByInputText() {
+ return "";
+ }
+
+ get _ariaLabelledByInputText() {
+ return "";
}
}
diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties
index a600a72135b3..eadb967ade56 100644
--- a/packages/main/src/i18n/messagebundle.properties
+++ b/packages/main/src/i18n/messagebundle.properties
@@ -498,6 +498,12 @@ MONTH_PICKER_DESCRIPTION = Month Picker
#XACT: ARIA description for year picker
YEAR_PICKER_DESCRIPTION = Year Picker
+#XACT: ARIA description for slider tooltip input
+SLIDER_TOOLTIP_INPUT_DESCRIPTION = Press F2 to enter a value
+
+#XACT: ARIA label for slider tooltip input
+SLIDER_TOOLTIP_INPUT_LABEL = Current Value
+
#XTOL: tooltip for decrease button of the StepInput
STEPINPUT_DEC_ICON_TITLE=Decrease
diff --git a/packages/main/src/themes/ColorPicker.css b/packages/main/src/themes/ColorPicker.css
index 7896930463ca..5bc5a98ae79b 100644
--- a/packages/main/src/themes/ColorPicker.css
+++ b/packages/main/src/themes/ColorPicker.css
@@ -62,9 +62,17 @@
width: 0.9375rem;
height: 1.5rem;
background: transparent;
+ box-sizing: border-box;
+}
+
+[ui5-slider]::part(handle-container) {
margin-inline-start: -2px;
margin-top: var(--_ui5_color_picker_slider_handle_margin_top);
- box-sizing: border-box;
+}
+
+[ui5-slider]::part(handle-container):focus-within {
+ margin-inline-start: unset;
+ margin-top: unset;
}
[ui5-slider]::part(handle)::after {
diff --git a/packages/main/src/themes/SliderBase.css b/packages/main/src/themes/SliderBase.css
index c3bcb2053b9e..1e642f9ca3a2 100644
--- a/packages/main/src/themes/SliderBase.css
+++ b/packages/main/src/themes/SliderBase.css
@@ -100,9 +100,7 @@
background: var(--_ui5_slider_handle_background);
border: var(--_ui5_slider_handle_border);
border-radius: var(--_ui5_slider_handle_border_radius);
- margin-inline-start: calc(-1 * var(--_ui5_slider_handle_width) / 2);
- top: var(--_ui5_slider_handle_top);
- position: absolute;
+ position: relative;
outline: none;
height: var(--_ui5_slider_handle_height);
width: var(--_ui5_slider_handle_width);
@@ -148,13 +146,17 @@
border: var(--_ui5_slider_handle_focus_border);
}
-.ui5-slider-tooltip {
+.ui5-slider-handle-container {
+ position: absolute;
+ margin-inline-start: calc(-1 * var(--_ui5_slider_handle_width) / 2);
+ top: var(--_ui5_slider_handle_top);
+}
+
+:host(:not([hidden])) .ui5-slider-handle-container .ui5-slider-tooltip {
display: flex;
justify-content: center;
align-items: center;
visibility: hidden;
- pointer-events: none;
- line-height: 1rem;
position: absolute;
left: 50%;
transform: translate(-50%);
@@ -171,6 +173,22 @@
box-sizing: var(--_ui5_slider_tooltip_border_box);
}
+:host(:not([hidden])):host([editable-tooltip]) .ui5-slider-handle-container .ui5-slider-tooltip {
+ border: none;
+ background: none;
+ box-shadow: none;
+}
+
+:host([editable-tooltip]) .ui5-slider-tooltip {
+ padding: 0;
+ box-shadow: none;
+}
+
+.ui5-slider-tooltip [ui5-input] {
+ width: 100%;
+ text-align: center;
+}
+
.ui5-slider-tooltip-value {
position: relative;
display: flex;
@@ -236,4 +254,4 @@
position: absolute;
border-radius: var(--_ui5_slider_handle_border_radius);
pointer-events: none;
-}
+}
\ No newline at end of file
diff --git a/packages/main/test/pages/RangeSlider.html b/packages/main/test/pages/RangeSlider.html
index 57f1b929379b..94282d41d2b9 100644
--- a/packages/main/test/pages/RangeSlider.html
+++ b/packages/main/test/pages/RangeSlider.html
@@ -40,8 +40,8 @@ Range Slider with tickmarks
Disabled Range Slider
- Range Slider with steps, tooltips, tickmarks and labels
-
+ Range Slider with steps, input tooltips, tickmarks and labels
+
@@ -71,6 +71,7 @@ Event Testing Result Slider