@@ -7,7 +7,7 @@ import { TextInput } from '../TextInput';
77import { Tooltip , TooltipProps } from '../Tooltip' ;
88import cssSliderValue from '@patternfly/react-tokens/dist/esm/c_slider_value' ;
99import cssFormControlWidthChars from '@patternfly/react-tokens/dist/esm/c_slider__value_c_form_control_width_chars' ;
10- import { getLanguageDirection } from '../../helpers/util' ;
10+ import { formatLocalizedDecimal , getLanguageDirection , parseLocalizedDecimal } from '../../helpers/util' ;
1111
1212/** Properties for creating custom steps in a slider. These properties should be passed in as
1313 * an object within an array to the slider component's customSteps property.
@@ -93,6 +93,8 @@ export interface SliderProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onCh
9393 thumbAriaValueText ?: string ;
9494 /** Current value of the slider. */
9595 value ?: number ;
96+ /** Locale string used for input formatting and parsing. See https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag for more information. */
97+ locale ?: string ;
9698}
9799
98100const getPercentage = ( current : number , max : number ) => ( 100 * current ) / max ;
@@ -126,13 +128,16 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
126128 showBoundaries = true ,
127129 'aria-describedby' : ariaDescribedby ,
128130 'aria-labelledby' : ariaLabelledby ,
131+ locale = 'en' ,
129132 ...props
130133} : SliderProps ) => {
131134 const sliderRailRef = useRef < HTMLDivElement > ( undefined ) ;
132135 const thumbRef = useRef < HTMLDivElement > ( undefined ) ;
136+ const isInputFocusedRef = useRef ( false ) ;
133137
134138 const [ localValue , setValue ] = useState ( value ) ;
135139 const [ localInputValue , setLocalInputValue ] = useState ( inputValue ) ;
140+ const [ inputDisplayValue , setInputDisplayValue ] = useState ( ( ) => formatLocalizedDecimal ( inputValue , 'en' ) ) ;
136141
137142 let isRTL : boolean ;
138143
@@ -144,46 +149,103 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
144149 setValue ( value ) ;
145150 } , [ value ] ) ;
146151
152+ const updateInputDisplay = useCallback ( ( numericValue : number ) => {
153+ setInputDisplayValue ( formatLocalizedDecimal ( numericValue , locale ) ) ;
154+ } , [ ] ) ;
155+
156+ const updateInputFromSliderValue = useCallback (
157+ ( numericValue : number ) => {
158+ setLocalInputValue ( numericValue ) ;
159+ if ( ! isInputFocusedRef . current ) {
160+ updateInputDisplay ( numericValue ) ;
161+ }
162+ } ,
163+ [ updateInputDisplay ]
164+ ) ;
165+
166+ const setLocalInputValueWithDisplay = useCallback < React . Dispatch < React . SetStateAction < number > > > (
167+ ( nextValue ) => {
168+ setLocalInputValue ( ( previousValue ) => {
169+ const resolvedValue = typeof nextValue === 'function' ? nextValue ( previousValue ) : nextValue ;
170+
171+ if ( ! isInputFocusedRef . current ) {
172+ updateInputDisplay ( resolvedValue ) ;
173+ }
174+
175+ return resolvedValue ;
176+ } ) ;
177+ } ,
178+ [ updateInputDisplay ]
179+ ) ;
180+
147181 useEffect ( ( ) => {
148- setLocalInputValue ( inputValue ) ;
149- } , [ inputValue ] ) ;
182+ if ( ! isInputFocusedRef . current ) {
183+ setLocalInputValue ( inputValue ) ;
184+ updateInputDisplay ( inputValue ) ;
185+ }
186+ } , [ inputValue , updateInputDisplay ] ) ;
150187
151188 let diff = 0 ;
152189 let snapValue : number ;
153190
154191 // calculate style value percentage
155192 const stylePercent = ( ( localValue - min ) * 100 ) / ( max - min ) ;
156193 const style = { [ cssSliderValue . name ] : `${ stylePercent } %` } as React . CSSProperties ;
157- const widthChars = useMemo ( ( ) => localInputValue . toString ( ) . length , [ localInputValue ] ) ;
194+ const widthChars = useMemo ( ( ) => inputDisplayValue . length || 1 , [ inputDisplayValue ] ) ;
158195 const inputStyle = { [ cssFormControlWidthChars . name ] : widthChars } as React . CSSProperties ;
159196
160197 const onChangeHandler = ( event : React . FormEvent < HTMLInputElement > , value : string ) => {
161- const newValue = Number ( value ) ;
162- setLocalInputValue ( newValue ) ;
198+ setInputDisplayValue ( value ) ;
199+
200+ const parsedValue = parseLocalizedDecimal ( value , locale ) ;
163201
164- isInputLive && onChange && onChange ( event , localValue , newValue , setLocalInputValue ) ;
202+ if ( ! Number . isNaN ( parsedValue ) ) {
203+ setLocalInputValue ( parsedValue ) ;
204+ isInputLive && onChange && onChange ( event , localValue , parsedValue , setLocalInputValueWithDisplay ) ;
205+ }
165206 } ;
166207
167208 const handleKeyPressOnInput = ( event : React . KeyboardEvent ) => {
168209 if ( event . key === 'Enter' ) {
169210 event . preventDefault ( ) ;
211+ const parsedValue = parseLocalizedDecimal ( inputDisplayValue , locale ) ;
212+
213+ if ( ! Number . isNaN ( parsedValue ) ) {
214+ setLocalInputValue ( parsedValue ) ;
215+ updateInputDisplay ( parsedValue ) ;
216+ }
217+
170218 if ( onChange ) {
171- onChange ( event , localValue , localInputValue , setLocalInputValue ) ;
219+ onChange (
220+ event ,
221+ localValue ,
222+ Number . isNaN ( parsedValue ) ? localInputValue : parsedValue ,
223+ setLocalInputValueWithDisplay
224+ ) ;
172225 }
173226 }
174227 } ;
175228
176- const onInputFocus = ( e : any ) => {
229+ const onInputFocus = ( e : React . SyntheticEvent < HTMLInputElement > ) => {
177230 e . stopPropagation ( ) ;
231+ isInputFocusedRef . current = true ;
178232 } ;
179233
180234 const onThumbClick = ( ) => {
181235 thumbRef . current . focus ( ) ;
182236 } ;
183237
184238 const onBlur = ( event : React . FocusEvent < HTMLInputElement > ) => {
239+ isInputFocusedRef . current = false ;
240+
241+ const parsedValue = parseLocalizedDecimal ( inputDisplayValue , locale ) ;
242+ const resolvedInputValue = Number . isNaN ( parsedValue ) ? localInputValue : parsedValue ;
243+
244+ setLocalInputValue ( resolvedInputValue ) ;
245+ updateInputDisplay ( resolvedInputValue ) ;
246+
185247 if ( onChange ) {
186- onChange ( event , localValue , localInputValue , setLocalInputValue ) ;
248+ onChange ( event , localValue , resolvedInputValue , setLocalInputValueWithDisplay ) ;
187249 }
188250 } ;
189251
@@ -240,8 +302,9 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
240302 if ( snapValue && ! areCustomStepsContinuous ) {
241303 thumbRef . current . style . setProperty ( cssSliderValue . name , `${ snapValue } %` ) ;
242304 setValue ( snapValue ) ;
305+ updateInputFromSliderValue ( snapValue ) ;
243306 if ( onChange ) {
244- onChange ( e , snapValue ) ;
307+ onChange ( e , snapValue , undefined , setLocalInputValueWithDisplay ) ;
245308 }
246309 }
247310 } ;
@@ -308,16 +371,22 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
308371 }
309372
310373 // Call onchange callback
374+ const resolvedValue = snapValue !== undefined ? snapValue : newValue ;
375+ updateInputFromSliderValue ( resolvedValue ) ;
376+
311377 if ( onChange ) {
312- if ( snapValue !== undefined ) {
313- onChange ( e , snapValue ) ;
314- } else {
315- onChange ( e , newValue ) ;
316- }
378+ onChange ( e , resolvedValue , undefined , setLocalInputValueWithDisplay ) ;
317379 }
318380 } ;
319381
320- const callbackThumbMove = useCallback ( handleThumbMove , [ min , max , customSteps , onChange ] ) ;
382+ const callbackThumbMove = useCallback ( handleThumbMove , [
383+ min ,
384+ max ,
385+ customSteps ,
386+ onChange ,
387+ updateInputFromSliderValue ,
388+ setLocalInputValueWithDisplay
389+ ] ) ;
321390 const callbackThumbUp = useCallback ( handleThumbDragEnd , [ min , max , customSteps , onChange ] ) ;
322391
323392 const handleThumbKeys = ( e : React . KeyboardEvent ) => {
@@ -373,8 +442,9 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
373442 if ( newValue !== localValue ) {
374443 thumbRef . current . style . setProperty ( cssSliderValue . name , `${ newValue } %` ) ;
375444 setValue ( newValue ) ;
445+ updateInputFromSliderValue ( newValue ) ;
376446 if ( onChange ) {
377- onChange ( e , newValue ) ;
447+ onChange ( e , newValue , undefined , setLocalInputValueWithDisplay ) ;
378448 }
379449 }
380450 } ;
@@ -383,8 +453,9 @@ export const Slider: React.FunctionComponent<SliderProps> = ({
383453 const textInput = (
384454 < TextInput
385455 isDisabled = { isDisabled }
386- type = "number"
387- value = { localInputValue }
456+ type = "text"
457+ inputMode = "decimal"
458+ value = { inputDisplayValue }
388459 aria-label = { inputAriaLabel }
389460 onKeyDown = { handleKeyPressOnInput }
390461 onChange = { onChangeHandler }
0 commit comments