Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3,680 changes: 1,865 additions & 1,815 deletions packages/main/cypress/specs/RangeSlider.cy.tsx

Large diffs are not rendered by default.

132 changes: 113 additions & 19 deletions packages/main/src/RangeSlider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isEnd,
isF2,
} from "@ui5/webcomponents-base/dist/Keys.js";
import { getAssociatedLabelForTexts } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js";
import SliderBase from "./SliderBase.js";
import RangeSliderTemplate from "./RangeSliderTemplate.js";
import type SliderTooltip from "./SliderTooltip.js";
Expand Down Expand Up @@ -93,7 +94,7 @@ type AffectedValue = "startValue" | "endValue";
languageAware: true,
formAssociated: true,
template: RangeSliderTemplate,
styles: [SliderBase.styles, rangeSliderStyles],
styles: [rangeSliderStyles],
})
class RangeSlider extends SliderBase implements IFormInputElement {
/**
Expand All @@ -106,7 +107,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
@property({ type: Number })
set startValue(value: number) {
this._startValue = value;
this.tooltipStartValue = value.toString();
this.tooltipStartValue = value?.toString() ?? "";
}

get startValue(): number {
Expand All @@ -123,7 +124,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
@property({ type: Number })
set endValue(value: number) {
this._endValue = value;
this.tooltipEndValue = value.toString();
this.tooltipEndValue = value?.toString() ?? "";
}

get endValue(): number {
Expand All @@ -145,6 +146,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
@property({ type: Boolean })
rangePressed = false;

@property({ type: Boolean })
_progressFocused = false;

@property({ type: Boolean })
_isStartValueValid = false;

Expand Down Expand Up @@ -192,6 +196,33 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this._stateStorage.endValue = undefined;
this._lastValidStartValue = this.min.toString();
this._lastValidEndValue = this.max.toString();
this._onDocumentClick = this._onDocumentClick.bind(this);
}

onEnterDOM() {
document.addEventListener("mousedown", this._onDocumentClick, true);
}

onExitDOM() {
document.removeEventListener("mousedown", this._onDocumentClick, true);
}

/**
* Handles document-level clicks to clear progress focus when clicking outside.
* @private
*/
_onDocumentClick(e: MouseEvent) {
const target = e.target as HTMLElement;
const clickedInside = e.composedPath().includes(this);

if (!clickedInside) {
if (this._progressFocused) {
this._progressFocused = false;
}
if (this._tooltipsOpen) {
this._tooltipsOpen = false;
}
}
}

get _ariaDisabled() {
Expand Down Expand Up @@ -326,6 +357,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this._setAffectedValue(undefined);
this._startValueInitial = undefined;
this._endValueInitial = undefined;
this._progressFocused = false;

if (this.showTooltip && !(e.relatedTarget as HTMLInputElement)?.hasAttribute("ui5-slider-tooltip")) {
this._tooltipsOpen = false;
Expand Down Expand Up @@ -358,7 +390,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
this._endValueAtBeginningOfAction = this.endValue;

if (isEscape(e)) {
this.update(undefined, this._startValueInitial, this._endValueInitial);
if (this._startValueInitial !== undefined && this._endValueInitial !== undefined) {
this.update(undefined, this._startValueInitial, this._endValueInitial);
}
return;
}

Expand Down Expand Up @@ -481,15 +515,30 @@ class RangeSlider extends SliderBase implements IFormInputElement {
return;
}

// Calculate the new value from the press position of the event
// Pre-calculate whether the press is in the current range before handleDownBase
// This is needed so focusInnerElement() knows where to focus
const ctor = this.constructor as typeof RangeSlider;
const pageX = ctor.getPageXValueFromEvent(e);
const tempValue = ctor.getValueFromInteraction(e, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart);
const isInRange = tempValue >= this.startValue && tempValue <= this.endValue;
const startHandle = this.shadowRoot!.querySelector("[ui5-slider-handle][handle-type='start']");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You already have getters for _startHandle and _endHandle. You can use them here instead of creating new variables startHandle and endHandle

const endHandle = this.shadowRoot!.querySelector("[ui5-slider-handle][handle-type='end']");
const inStartHandle = startHandle && pageX >= startHandle.getBoundingClientRect().left && pageX <= startHandle.getBoundingClientRect().right;
const inEndHandle = endHandle && pageX >= endHandle.getBoundingClientRect().left && pageX <= endHandle.getBoundingClientRect().right;

if (isInRange && !inStartHandle && !inEndHandle) {
this._setIsPressInCurrentRange(true);
this._progressFocused = true;
this.rangePressed = true;
} else {
this._progressFocused = false;
this.rangePressed = false;
}

const newValue = this.handleDownBase(e);

// Determine the rest of the needed details from the start of the interaction.
this._saveInteractionStartData(e, newValue);

this.rangePressed = this._isPressInCurrentRange;

// Do not yet update the RangeSlider if press is in range or over a handle.
if (this._isPressInCurrentRange || this._handeIsPressed) {
this._handeIsPressed = false;
return;
Expand All @@ -509,7 +558,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
* @private
*/
_saveInteractionStartData(e: TouchEvent | MouseEvent, newValue: number) {
const progressBarDom = this.shadowRoot!.querySelector(".ui5-slider-progress")!.getBoundingClientRect();
const progressBarDom = this._progressBar?.getBoundingClientRect();

// Save the state of the value properties on the start of the interaction
this._startValueAtBeginningOfAction = this.startValue;
Expand All @@ -521,7 +570,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
// 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.
this._initialStartHandlePageX = this.directionStart === "left" ? progressBarDom.left : progressBarDom.right;
if (progressBarDom) {
this._initialStartHandlePageX = this.directionStart === "left" ? progressBarDom.left : progressBarDom.right;
}
}

/**
Expand Down Expand Up @@ -606,8 +657,8 @@ class RangeSlider extends SliderBase implements IFormInputElement {
* @private
*/
_pressTargetAndAffectedValue(clientX: number, value: number) {
const startHandle = this.shadowRoot!.querySelector(".ui5-slider-handle--start")!;
const endHandle = this.shadowRoot!.querySelector(".ui5-slider-handle--end")!;
const startHandle = this.shadowRoot!.querySelector("[ui5-slider-handle][handle-type='start']")!;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use the getters _startHandle and _endHandle instead of creating new constants

const endHandle = this.shadowRoot!.querySelector("[ui5-slider-handle][handle-type='end']")!;

// Check if the press point is in the bounds of any of the Range Slider handles
const handleStartDomRect = startHandle.getBoundingClientRect();
Expand Down Expand Up @@ -690,16 +741,16 @@ class RangeSlider extends SliderBase implements IFormInputElement {
const affectedValue = this._valueAffected;

if (this._isPressInCurrentRange || !affectedValue) {
this._progressBar.focus();
this._progressBar?.focus();
}

if ((affectedValue === "startValue" && !isReversed) || (affectedValue === "endValue" && isReversed)) {
this._startHandle.focus();
this._startHandle?.focus();
this.bringToFrontTooltip("start");
}

if ((affectedValue === "endValue" && !isReversed) || (affectedValue === "startValue" && isReversed)) {
this._endHandle.focus();
this._endHandle?.focus();
this.bringToFrontTooltip("end");
}
}
Expand Down Expand Up @@ -1004,15 +1055,16 @@ class RangeSlider extends SliderBase implements IFormInputElement {
}

get _startHandle() {
return this.shadowRoot!.querySelector<HTMLElement>(".ui5-slider-handle--start")!;
return this.shadowRoot!.querySelector<HTMLElement>("[ui5-slider-handle][handle-type='start']")!;
}

get _endHandle() {
return this.shadowRoot!.querySelector<HTMLElement>(".ui5-slider-handle--end")!;
return this.shadowRoot!.querySelector<HTMLElement>("[ui5-slider-handle][handle-type='end']")!;
}

get _progressBar() {
return this.shadowRoot!.querySelector<HTMLElement>(".ui5-slider-progress")!;
const sliderScale = this.shadowRoot!.querySelector<HTMLElement>("[ui5-slider-scale]");
return sliderScale?.shadowRoot?.querySelector<HTMLElement>(".ui5-slider-progress") ?? null;
}

get _ariaLabelledByStartHandleText() {
Expand All @@ -1023,6 +1075,48 @@ class RangeSlider extends SliderBase implements IFormInputElement {
return this.accessibleName ? ["ui5-slider-accName", "ui5-slider-endHandleDesc"].join(" ").trim() : "ui5-slider-endHandleDesc";
}

/**
* Returns the aria-label for the start handle, including associated label text
* from <label for="..."> elements, accessibleName, and the handle description.
* @private
*/
get _ariaLabelStartHandle() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

_ariaLabelStartHandle and _ariaLabelEndHandle are identical and differ only how handleDescription constant is obtained. You can think of improving this code redundancy

const associatedLabelText = getAssociatedLabelForTexts(this);
const hasAccessibleName = !!this.accessibleName;
const handleDescription = this._ariaHandlesText.startHandleText;

let labelText = hasAccessibleName
? `${this.accessibleName} ${handleDescription}`
: handleDescription;

if (!hasAccessibleName && associatedLabelText) {
labelText = `${associatedLabelText} ${labelText}`;
}

return labelText;
}

/**
* Returns the aria-label for the end handle, including associated label text
* from <label for="..."> elements, accessibleName, and the handle description.
* @private
*/
get _ariaLabelEndHandle() {
const associatedLabelText = getAssociatedLabelForTexts(this);
const hasAccessibleName = !!this.accessibleName;
const handleDescription = this._ariaHandlesText.endHandleText;

let labelText = hasAccessibleName
? `${this.accessibleName} ${handleDescription}`
: handleDescription;

if (!hasAccessibleName && associatedLabelText) {
labelText = `${associatedLabelText} ${labelText}`;
}

return labelText;
}

get _ariaLabelledByInputText() {
return RangeSlider.i18nBundle.getText(SLIDER_TOOLTIP_INPUT_LABEL);
}
Expand Down
Loading
Loading