From 33b7084af0e71c7c70a4b4d1e066c33f6d376fee Mon Sep 17 00:00:00 2001 From: aleksandar-terziev Date: Wed, 23 Sep 2026 16:57:35 +0300 Subject: [PATCH] fix(ui5-range-slider): mobile focus support --- packages/main/src/RangeSlider.ts | 7 ++++--- packages/main/src/RangeSliderTemplate.tsx | 3 +++ packages/main/src/Slider.ts | 5 +++++ packages/main/src/SliderBase.ts | 9 ++++++++- packages/main/src/SliderHandle.ts | 6 ++++++ packages/main/src/SliderScale.ts | 12 ++++++++++++ packages/main/src/SliderScaleTemplate.tsx | 2 +- packages/main/src/SliderTemplate.tsx | 1 + packages/main/src/themes/SliderHandle.css | 19 ++++++++++++------- packages/main/src/themes/SliderScale.css | 6 ++++-- 10 files changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/main/src/RangeSlider.ts b/packages/main/src/RangeSlider.ts index e40b62d1a719d..a2f1a8cce2793 100644 --- a/packages/main/src/RangeSlider.ts +++ b/packages/main/src/RangeSlider.ts @@ -1,3 +1,4 @@ +import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; @@ -774,16 +775,16 @@ class RangeSlider extends SliderBase implements IFormInputElement { const affectedValue = this._valueAffected; if (this._isPressInCurrentRange || !affectedValue) { - this._progressBar?.focus(); + this._progressBar?.focus({ focusVisible: isDesktop() } as FocusOptions); } if ((affectedValue === "startValue" && !isReversed) || (affectedValue === "endValue" && isReversed)) { - this._startHandle?.focus(); + this._startHandle?.focus({ focusVisible: isDesktop() } as FocusOptions); this.bringToFrontTooltip("start"); } if ((affectedValue === "endValue" && !isReversed) || (affectedValue === "startValue" && isReversed)) { - this._endHandle?.focus(); + this._endHandle?.focus({ focusVisible: isDesktop() } as FocusOptions); this.bringToFrontTooltip("end"); } } diff --git a/packages/main/src/RangeSliderTemplate.tsx b/packages/main/src/RangeSliderTemplate.tsx index 687710aecdfef..2370b18c29977 100644 --- a/packages/main/src/RangeSliderTemplate.tsx +++ b/packages/main/src/RangeSliderTemplate.tsx @@ -20,6 +20,7 @@ const startHandle = (slider: RangeSlider) => { max={slider.max} tabIndex={slider._tabIndex} active={slider.rangePressed} + desktop={slider._isDesktop} handleType={SliderHandleType.Start} aria-orientation="horizontal" part="handle" @@ -55,6 +56,7 @@ const endHandle = (slider: RangeSlider) => { max={slider.max} tabIndex={slider._tabIndex} active={slider.rangePressed} + desktop={slider._isDesktop} handleType={SliderHandleType.End} aria-orientation="horizontal" part="handle" @@ -147,6 +149,7 @@ export default function RangeSliderTemplate(this: RangeSlider) { progressAriaDisabled={this._ariaDisabled} progressPressed={this.rangePressed} progressFocused={this._progressFocused} + desktop={this._isDesktop} onFocusIn={this._onfocusin} onFocusOut={this._onfocusout} part="scale" diff --git a/packages/main/src/Slider.ts b/packages/main/src/Slider.ts index 13b9e1277290f..5d3384d7cc037 100644 --- a/packages/main/src/Slider.ts +++ b/packages/main/src/Slider.ts @@ -1,4 +1,5 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; @@ -352,6 +353,10 @@ class Slider extends SliderBase implements IFormInputElement { return this.getDomRef()?.querySelector("[ui5-slider-tooltip]"); } + focusInnerElement() { + this._sliderHandle.focus({ focusVisible: isDesktop() } as FocusOptions); + } + get _sliderHandle() : HTMLElement { return this.shadowRoot!.querySelector("[ui5-slider-handle]")!; } diff --git a/packages/main/src/SliderBase.ts b/packages/main/src/SliderBase.ts index e860aa2e4d9e4..cf23dbb018be0 100644 --- a/packages/main/src/SliderBase.ts +++ b/packages/main/src/SliderBase.ts @@ -3,7 +3,7 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import jsxRender from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; -import { isPhone, supportsTouch } from "@ui5/webcomponents-base/dist/Device.js"; +import { isDesktop, isPhone, supportsTouch } from "@ui5/webcomponents-base/dist/Device.js"; import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import { isEscape, isHome, isEnd, isUp, isDown, isRight, isLeft, isUpCtrl, isDownCtrl, isRightCtrl, isLeftCtrl, isPlus, isMinus, isPageUp, isPageDown, isF2, @@ -296,6 +296,9 @@ abstract class SliderBase extends UI5Element { } onEnterDOM() { + if (isDesktop()) { + this.setAttribute("desktop", ""); + } ResizeHandler.register(this, this._resizeHandler); } @@ -800,6 +803,10 @@ abstract class SliderBase extends UI5Element { return this.disabled ? -1 : 0; } + get _isDesktop() { + return isDesktop(); + } + get _ariaDescribedByHandleText() { return this.editableTooltip ? "ui5-slider-InputDesc" : undefined; } diff --git a/packages/main/src/SliderHandle.ts b/packages/main/src/SliderHandle.ts index 989777d1ad68b..6017f3080ee4a 100644 --- a/packages/main/src/SliderHandle.ts +++ b/packages/main/src/SliderHandle.ts @@ -81,6 +81,12 @@ class SliderHandle extends UI5Element { @property({ type: Boolean }) active = false; + /** + * @private + */ + @property({ type: Boolean }) + desktop = false; + /** * Defines whether the slider handle should appear hovered. *

diff --git a/packages/main/src/SliderScale.ts b/packages/main/src/SliderScale.ts index 812e735581bfc..821e3d2d918bb 100644 --- a/packages/main/src/SliderScale.ts +++ b/packages/main/src/SliderScale.ts @@ -171,6 +171,12 @@ class SliderScale extends UI5Element { @property({ type: Boolean }) progressFocused = false; + /** + * @private + */ + @property({ type: Boolean }) + desktop = false; + /** * Indicates that the progress bar is being hovered. * @default false @@ -288,6 +294,9 @@ class SliderScale extends UI5Element { } _onProgressMouseEnter() { + if (!this.desktop) { + return; + } this.progressHovered = true; this.handles.forEach(handle => { if (!handle.active) { @@ -297,6 +306,9 @@ class SliderScale extends UI5Element { } _onProgressMouseLeave() { + if (!this.desktop) { + return; + } this.progressHovered = false; this.handles.forEach(handle => { handle.hovered = false; diff --git a/packages/main/src/SliderScaleTemplate.tsx b/packages/main/src/SliderScaleTemplate.tsx index 5b329bd50db26..9fcc4d5a3c6ae 100644 --- a/packages/main/src/SliderScaleTemplate.tsx +++ b/packages/main/src/SliderScaleTemplate.tsx @@ -29,7 +29,7 @@ export default function SliderScaleTemplate(this: SliderScale) { class={{ "ui5-slider-scale-progress": true, "ui5-slider-progress": true, - "ui5-slider-progress--focused": this.progressFocused || this.progressPressed, + "ui5-slider-progress--focused": this.desktop && (this.progressFocused || this.progressPressed), }} part="progress" style={this._progressStyle} diff --git a/packages/main/src/SliderTemplate.tsx b/packages/main/src/SliderTemplate.tsx index 1b56044f75a57..9c097e4793024 100644 --- a/packages/main/src/SliderTemplate.tsx +++ b/packages/main/src/SliderTemplate.tsx @@ -20,6 +20,7 @@ const handle = (slider: Slider) => { min={slider.min} max={slider.max} tabIndex={slider.disabled ? -1 : 0} + desktop={slider._isDesktop} aria-orientation="horizontal" part="handle" exportparts="icon: handle-icon" diff --git a/packages/main/src/themes/SliderHandle.css b/packages/main/src/themes/SliderHandle.css index 9a7af8f561ed1..f94a48f514d86 100644 --- a/packages/main/src/themes/SliderHandle.css +++ b/packages/main/src/themes/SliderHandle.css @@ -18,13 +18,14 @@ transform: translateY(-50%); } -:host(:focus) { +:host([desktop]:focus), +:host(:focus-visible) { outline: var(--ui5_slider_handle_outline); outline-offset: var(--ui5_slider_handle_outline_offset); z-index: 6; } -:host(:hover), +:host([desktop]:hover), :host([hovered]) { background: var(--_ui5_slider_handle_hover_background); border: var(--_ui5_slider_handle_hover_border); @@ -46,17 +47,21 @@ height: var(--_ui5_slider_handle_icon_size); } -:host(:focus), +:host([desktop]:focus), +:host(:focus-visible), :host([active]), :host(:active) { - [slider-icon] { - display: none; - } - background-color: var(--_ui5_slider_handle_background_focus); border: var(--_ui5_slider_handle_focus_border); } +:host([desktop]:focus) [slider-icon], +:host(:focus-visible) [slider-icon], +:host([active]) [slider-icon], +:host(:active) [slider-icon] { + display: none; +} + :host([orientation="Vertical"]) { transform: rotate(90deg); } \ No newline at end of file diff --git a/packages/main/src/themes/SliderScale.css b/packages/main/src/themes/SliderScale.css index 39dc22770891f..c58c804b4ddee 100644 --- a/packages/main/src/themes/SliderScale.css +++ b/packages/main/src/themes/SliderScale.css @@ -76,7 +76,8 @@ outline: none; } -.ui5-slider-scale-progress:focus::after, +:host([desktop]) .ui5-slider-scale-progress:focus::after, +.ui5-slider-scale-progress:focus-visible::after, .ui5-slider-scale-progress.ui5-slider-progress--focused::after { border: var(--_ui5_range_slider_progress_focus_border); border-radius: var(--_ui5_range_slider_progress_focus_border_radius, 0.5rem); @@ -91,7 +92,8 @@ box-sizing: border-box; } -.ui5-slider-scale-progress:focus::before, +:host([desktop]) .ui5-slider-scale-progress:focus::before, +.ui5-slider-scale-progress:focus-visible::before, .ui5-slider-scale-progress.ui5-slider-progress--focused::before { display: var(--_ui5_range_slider_legacy_progress_focus_display); content: '';