@@ -11,6 +11,7 @@ import {
1111 isEnd ,
1212 isF2 ,
1313} from "@ui5/webcomponents-base/dist/Keys.js" ;
14+ import { getAssociatedLabelForTexts } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js" ;
1415import SliderBase from "./SliderBase.js" ;
1516import RangeSliderTemplate from "./RangeSliderTemplate.js" ;
1617import type SliderTooltip from "./SliderTooltip.js" ;
@@ -93,7 +94,7 @@ type AffectedValue = "startValue" | "endValue";
9394 languageAware : true ,
9495 formAssociated : true ,
9596 template : RangeSliderTemplate ,
96- styles : [ SliderBase . styles , rangeSliderStyles ] ,
97+ styles : [ rangeSliderStyles ] ,
9798} )
9899class RangeSlider extends SliderBase implements IFormInputElement {
99100 /**
@@ -106,7 +107,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
106107 @property ( { type : Number } )
107108 set startValue ( value : number ) {
108109 this . _startValue = value ;
109- this . tooltipStartValue = value . toString ( ) ;
110+ this . tooltipStartValue = value ? .toString ( ) ?? "" ;
110111 }
111112
112113 get startValue ( ) : number {
@@ -123,7 +124,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
123124 @property ( { type : Number } )
124125 set endValue ( value : number ) {
125126 this . _endValue = value ;
126- this . tooltipEndValue = value . toString ( ) ;
127+ this . tooltipEndValue = value ? .toString ( ) ?? "" ;
127128 }
128129
129130 get endValue ( ) : number {
@@ -145,6 +146,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
145146 @property ( { type : Boolean } )
146147 rangePressed = false ;
147148
149+ @property ( { type : Boolean } )
150+ _progressFocused = false ;
151+
148152 @property ( { type : Boolean } )
149153 _isStartValueValid = false ;
150154
@@ -169,6 +173,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
169173 _lastValidStartValue : string ;
170174 _lastValidEndValue : string ;
171175 _areInputValuesSwapped = false ;
176+ _onDocumentClickBound : ( e : MouseEvent ) => void ;
172177
173178 @i18n ( "@ui5/webcomponents" )
174179 static i18nBundle : I18nBundle ;
@@ -192,6 +197,29 @@ class RangeSlider extends SliderBase implements IFormInputElement {
192197 this . _stateStorage . endValue = undefined ;
193198 this . _lastValidStartValue = this . min . toString ( ) ;
194199 this . _lastValidEndValue = this . max . toString ( ) ;
200+ this . _onDocumentClickBound = this . _onDocumentClick . bind ( this ) ;
201+ }
202+
203+ onEnterDOM ( ) {
204+ document . addEventListener ( "mousedown" , this . _onDocumentClickBound , true ) ;
205+ }
206+
207+ onExitDOM ( ) {
208+ document . removeEventListener ( "mousedown" , this . _onDocumentClickBound , true ) ;
209+ }
210+
211+ /**
212+ * Handles document-level clicks to clear progress focus when clicking outside.
213+ * @private
214+ */
215+ _onDocumentClick ( e : MouseEvent ) {
216+ const clickedInside = e . composedPath ( ) . includes ( this ) ;
217+
218+ if ( ! clickedInside ) {
219+ if ( this . _tooltipsOpen ) {
220+ this . _tooltipsOpen = false ;
221+ }
222+ }
195223 }
196224
197225 get _ariaDisabled ( ) {
@@ -326,6 +354,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
326354 this . _setAffectedValue ( undefined ) ;
327355 this . _startValueInitial = undefined ;
328356 this . _endValueInitial = undefined ;
357+ this . _progressFocused = false ;
329358
330359 if ( this . showTooltip && ! ( e . relatedTarget as HTMLInputElement ) ?. hasAttribute ( "ui5-slider-tooltip" ) ) {
331360 this . _tooltipsOpen = false ;
@@ -358,7 +387,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
358387 this . _endValueAtBeginningOfAction = this . endValue ;
359388
360389 if ( isEscape ( e ) ) {
361- this . update ( undefined , this . _startValueInitial , this . _endValueInitial ) ;
390+ if ( this . _startValueInitial !== undefined && this . _endValueInitial !== undefined ) {
391+ this . update ( undefined , this . _startValueInitial , this . _endValueInitial ) ;
392+ }
362393 return ;
363394 }
364395
@@ -387,13 +418,14 @@ class RangeSlider extends SliderBase implements IFormInputElement {
387418
388419 // Update a single value if one of the handles is focused or the range if not already at min or max
389420 const ctor = this . constructor as typeof RangeSlider ;
421+ const stepPrecision = ctor . _getDecimalPrecisionOfNumber ( this . _effectiveStep ) ;
390422 if ( affectedValue && ! this . _isPressInCurrentRange ) {
391423 const propValue = this [ affectedValue as keyof RangeSlider ] as number ;
392- const newValue = ctor . clipValue ( newValueOffset + propValue , min , max ) ;
424+ const newValue = Number ( ctor . clipValue ( newValueOffset + propValue , min , max ) . toFixed ( stepPrecision ) ) ;
393425 this . update ( affectedValue , newValue , undefined ) ;
394426 } else if ( ( newValueOffset < 0 && this . startValue > min ) || ( newValueOffset > 0 && this . endValue < max ) ) {
395- const newStartValue = ctor . clipValue ( newValueOffset + this . startValue , min , max ) ;
396- const newEndValue = ctor . clipValue ( newValueOffset + this . endValue , min , max ) ;
427+ const newStartValue = Number ( ctor . clipValue ( newValueOffset + this . startValue , min , max ) . toFixed ( stepPrecision ) ) ;
428+ const newEndValue = Number ( ctor . clipValue ( newValueOffset + this . endValue , min , max ) . toFixed ( stepPrecision ) ) ;
397429 this . update ( affectedValue , newStartValue , newEndValue ) ;
398430 }
399431
@@ -415,7 +447,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
415447 this . _setAffectedValue ( "endValue" ) ;
416448 }
417449
418- if ( this . shadowRoot ! . activeElement === this . _progressBar ) {
450+ // Progress bar is inside SliderScale's shadow DOM, so check the nested activeElement
451+ const sliderScale = this . shadowRoot ! . querySelector < HTMLElement > ( "[ui5-slider-scale]" ) ;
452+ if ( sliderScale ?. shadowRoot ?. activeElement === this . _progressBar ) {
419453 this . _setAffectedValue ( undefined ) ;
420454 }
421455
@@ -430,8 +464,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
430464 _homeEndForSelectedRange ( e : KeyboardEvent , affectedValue : string , min : number , max : number ) {
431465 const newValueOffset = this . _handleActionKeyPressBase ( e , affectedValue ) ;
432466 const ctor = this . constructor as typeof RangeSlider ;
433- const newStartValue = ctor . clipValue ( newValueOffset + this . startValue , min , max ) ;
434- const newEndValue = ctor . clipValue ( newValueOffset + this . endValue , min , max ) ;
467+ const stepPrecision = ctor . _getDecimalPrecisionOfNumber ( this . _effectiveStep ) ;
468+ const newStartValue = Number ( ctor . clipValue ( newValueOffset + this . startValue , min , max ) . toFixed ( stepPrecision ) ) ;
469+ const newEndValue = Number ( ctor . clipValue ( newValueOffset + this . endValue , min , max ) . toFixed ( stepPrecision ) ) ;
435470
436471 this . update ( undefined , newStartValue , newEndValue ) ;
437472 }
@@ -481,15 +516,30 @@ class RangeSlider extends SliderBase implements IFormInputElement {
481516 return ;
482517 }
483518
484- // Calculate the new value from the press position of the event
519+ // Pre-calculate whether the press is in the current range before handleDownBase
520+ // This is needed so focusInnerElement() knows where to focus
521+ const ctor = this . constructor as typeof RangeSlider ;
522+ const pageX = ctor . getPageXValueFromEvent ( e ) ;
523+ const tempValue = ctor . getValueFromInteraction ( e , this . _effectiveStep , this . _effectiveMin , this . _effectiveMax , this . getBoundingClientRect ( ) , this . directionStart ) ;
524+ const isInRange = tempValue >= this . startValue && tempValue <= this . endValue ;
525+ const startHandle = this . _startHandle ;
526+ const endHandle = this . _endHandle ;
527+ const inStartHandle = startHandle && pageX >= startHandle . getBoundingClientRect ( ) . left && pageX <= startHandle . getBoundingClientRect ( ) . right ;
528+ const inEndHandle = endHandle && pageX >= endHandle . getBoundingClientRect ( ) . left && pageX <= endHandle . getBoundingClientRect ( ) . right ;
529+
485530 const newValue = this . handleDownBase ( e ) ;
486531
487- // Determine the rest of the needed details from the start of the interaction.
488- this . _saveInteractionStartData ( e , newValue ) ;
532+ if ( isInRange && ! inStartHandle && ! inEndHandle ) {
533+ this . _setIsPressInCurrentRange ( true ) ;
534+ this . _progressFocused = true ;
535+ this . rangePressed = true ;
536+ } else {
537+ this . _progressFocused = false ;
538+ this . rangePressed = false ;
539+ }
489540
490- this . rangePressed = this . _isPressInCurrentRange ;
541+ this . _saveInteractionStartData ( e , newValue ) ;
491542
492- // Do not yet update the RangeSlider if press is in range or over a handle.
493543 if ( this . _isPressInCurrentRange || this . _handeIsPressed ) {
494544 this . _handeIsPressed = false ;
495545 return ;
@@ -509,7 +559,7 @@ class RangeSlider extends SliderBase implements IFormInputElement {
509559 * @private
510560 */
511561 _saveInteractionStartData ( e : TouchEvent | MouseEvent , newValue : number ) {
512- const progressBarDom = this . shadowRoot ! . querySelector ( ".ui5-slider-progress" ) ! . getBoundingClientRect ( ) ;
562+ const progressBarDom = this . _progressBar ? .getBoundingClientRect ( ) ;
513563
514564 // Save the state of the value properties on the start of the interaction
515565 this . _startValueAtBeginningOfAction = this . startValue ;
@@ -521,7 +571,9 @@ class RangeSlider extends SliderBase implements IFormInputElement {
521571 // Which element of the Range Slider is pressed and which value property to be modified on further interaction
522572 this . _pressTargetAndAffectedValue ( this . _initialPageXPosition , newValue ) ;
523573 // Use the progress bar to save the initial coordinates of the start-handle when the interaction begins.
524- this . _initialStartHandlePageX = this . directionStart === "left" ? progressBarDom . left : progressBarDom . right ;
574+ if ( progressBarDom ) {
575+ this . _initialStartHandlePageX = this . directionStart === "left" ? progressBarDom . left : progressBarDom . right ;
576+ }
525577 }
526578
527579 /**
@@ -606,8 +658,8 @@ class RangeSlider extends SliderBase implements IFormInputElement {
606658 * @private
607659 */
608660 _pressTargetAndAffectedValue ( clientX : number , value : number ) {
609- const startHandle = this . shadowRoot ! . querySelector ( ".ui5-slider-handle--start" ) ! ;
610- const endHandle = this . shadowRoot ! . querySelector ( ".ui5-slider-handle--end" ) ! ;
661+ const startHandle = this . _startHandle ;
662+ const endHandle = this . _endHandle ;
611663
612664 // Check if the press point is in the bounds of any of the Range Slider handles
613665 const handleStartDomRect = startHandle . getBoundingClientRect ( ) ;
@@ -690,16 +742,16 @@ class RangeSlider extends SliderBase implements IFormInputElement {
690742 const affectedValue = this . _valueAffected ;
691743
692744 if ( this . _isPressInCurrentRange || ! affectedValue ) {
693- this . _progressBar . focus ( ) ;
745+ this . _progressBar ? .focus ( ) ;
694746 }
695747
696748 if ( ( affectedValue === "startValue" && ! isReversed ) || ( affectedValue === "endValue" && isReversed ) ) {
697- this . _startHandle . focus ( ) ;
749+ this . _startHandle ? .focus ( ) ;
698750 this . bringToFrontTooltip ( "start" ) ;
699751 }
700752
701753 if ( ( affectedValue === "endValue" && ! isReversed ) || ( affectedValue === "startValue" && isReversed ) ) {
702- this . _endHandle . focus ( ) ;
754+ this . _endHandle ? .focus ( ) ;
703755 this . bringToFrontTooltip ( "end" ) ;
704756 }
705757 }
@@ -732,7 +784,11 @@ class RangeSlider extends SliderBase implements IFormInputElement {
732784 const ctor = this . constructor as typeof RangeSlider ;
733785 startValue = ctor . clipValue ( startValue , min , max - selectedRange ) ;
734786
735- return [ startValue , startValue + selectedRange ] ;
787+ const stepPrecision = ctor . _getDecimalPrecisionOfNumber ( this . _effectiveStep ) ;
788+ const endValue = Number ( ( startValue + selectedRange ) . toFixed ( stepPrecision ) ) ;
789+ startValue = Number ( startValue . toFixed ( stepPrecision ) ) ;
790+
791+ return [ startValue , endValue ] ;
736792 }
737793
738794 /**
@@ -830,6 +886,12 @@ class RangeSlider extends SliderBase implements IFormInputElement {
830886 }
831887
832888 _onTooltipChange ( e : CustomEvent ) {
889+ // Skip if this is a focusout change event triggered by the swap focus change
890+ if ( this . _areInputValuesSwapped ) {
891+ this . _areInputValuesSwapped = false ;
892+ return ;
893+ }
894+
833895 const tooltip = e . target as SliderTooltip ;
834896 const isStart = tooltip . hasAttribute ( "data-sap-ui-start-value" ) ;
835897 const inputValue = parseFloat ( e . detail . value as string ) ;
@@ -849,9 +911,11 @@ class RangeSlider extends SliderBase implements IFormInputElement {
849911 const clampedValue = Math . min ( this . max , Math . max ( this . min , inputValue ) ) ;
850912
851913 if ( isStart ) {
914+ this . tooltipStartValueState = ValueState . None ;
852915 this . startValue = clampedValue ;
853916 this . _lastValidStartValue = clampedValue . toString ( ) ;
854917 } else {
918+ this . tooltipEndValueState = ValueState . None ;
855919 this . endValue = clampedValue ;
856920 this . _lastValidEndValue = clampedValue . toString ( ) ;
857921 }
@@ -898,10 +962,12 @@ class RangeSlider extends SliderBase implements IFormInputElement {
898962 }
899963
900964 _onTooltipOpen ( ) {
901- const ctor = this . constructor as typeof RangeSlider ;
902- const stepPrecision = ctor . _getDecimalPrecisionOfNumber ( this . _effectiveStep ) ;
903- this . tooltipStartValue = this . startValue . toFixed ( stepPrecision ) ;
904- this . tooltipEndValue = this . endValue . toFixed ( stepPrecision ) ;
965+ if ( ! this . startValue || ! this . endValue ) {
966+ return ;
967+ }
968+
969+ this . tooltipStartValue = this . startValue . toString ( ) ;
970+ this . tooltipEndValue = this . endValue . toString ( ) ;
905971 }
906972
907973 _onTooltipInput ( e : CustomEvent ) {
@@ -1004,15 +1070,16 @@ class RangeSlider extends SliderBase implements IFormInputElement {
10041070 }
10051071
10061072 get _startHandle ( ) {
1007- return this . shadowRoot ! . querySelector < HTMLElement > ( ". ui5-slider-handle--start " ) ! ;
1073+ return this . shadowRoot ! . querySelector < HTMLElement > ( "[ ui5-slider-handle][handle-type='Start'] " ) ! ;
10081074 }
10091075
10101076 get _endHandle ( ) {
1011- return this . shadowRoot ! . querySelector < HTMLElement > ( ". ui5-slider-handle--end " ) ! ;
1077+ return this . shadowRoot ! . querySelector < HTMLElement > ( "[ ui5-slider-handle][handle-type='End'] " ) ! ;
10121078 }
10131079
10141080 get _progressBar ( ) {
1015- return this . shadowRoot ! . querySelector < HTMLElement > ( ".ui5-slider-progress" ) ! ;
1081+ const sliderScale = this . shadowRoot ! . querySelector < HTMLElement > ( "[ui5-slider-scale]" ) ;
1082+ return sliderScale ?. shadowRoot ?. querySelector < HTMLElement > ( ".ui5-slider-progress" ) ?? null ;
10161083 }
10171084
10181085 get _ariaLabelledByStartHandleText ( ) {
@@ -1023,6 +1090,35 @@ class RangeSlider extends SliderBase implements IFormInputElement {
10231090 return this . accessibleName ? [ "ui5-slider-accName" , "ui5-slider-endHandleDesc" ] . join ( " " ) . trim ( ) : "ui5-slider-endHandleDesc" ;
10241091 }
10251092
1093+ /**
1094+ * @private
1095+ */
1096+ get _ariaLabelStartHandle ( ) {
1097+ return this . _getAriaLabelHandle ( this . _ariaHandlesText . startHandleText || "" ) ;
1098+ }
1099+
1100+ /**
1101+ * @private
1102+ */
1103+ get _ariaLabelEndHandle ( ) {
1104+ return this . _getAriaLabelHandle ( this . _ariaHandlesText . endHandleText || "" ) ;
1105+ }
1106+
1107+ _getAriaLabelHandle ( handleDescription : string ) {
1108+ const associatedLabelText = getAssociatedLabelForTexts ( this ) ;
1109+ const hasAccessibleName = ! ! this . accessibleName ;
1110+
1111+ let labelText = hasAccessibleName
1112+ ? `${ this . accessibleName } ${ handleDescription } `
1113+ : handleDescription ;
1114+
1115+ if ( ! hasAccessibleName && associatedLabelText ) {
1116+ labelText = `${ associatedLabelText } ${ labelText } ` ;
1117+ }
1118+
1119+ return labelText ;
1120+ }
1121+
10261122 get _ariaLabelledByInputText ( ) {
10271123 return RangeSlider . i18nBundle . getText ( SLIDER_TOOLTIP_INPUT_LABEL ) ;
10281124 }
0 commit comments