-
Notifications
You must be signed in to change notification settings - Fork 278
wip(ui5-range-slider): refactor ui5-range-slider component #13316
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
base: main
Are you sure you want to change the base?
Changes from all commits
32a1249
e440176
f0189fc
7500c73
493960a
a6b9496
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"; | ||
|
|
@@ -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 { | ||
| /** | ||
|
|
@@ -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 { | ||
|
|
@@ -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 { | ||
|
|
@@ -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; | ||
|
|
||
|
|
@@ -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() { | ||
|
|
@@ -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; | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
|
|
@@ -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']"); | ||
| 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; | ||
|
|
@@ -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; | ||
|
|
@@ -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; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -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']")!; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
|
@@ -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"); | ||
| } | ||
| } | ||
|
|
@@ -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() { | ||
|
|
@@ -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() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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