@@ -8,22 +8,24 @@ import type { BaseUIComponentProps, Orientation } from '../../utils/types';
8
8
import { useBaseUiId } from '../../utils/useBaseUiId' ;
9
9
import { useControlled } from '../../utils/useControlled' ;
10
10
import { useEventCallback } from '../../utils/useEventCallback' ;
11
+ import { useForkRef } from '../../utils/useForkRef' ;
11
12
import { useLatestRef } from '../../utils/useLatestRef' ;
12
13
import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect' ;
13
14
import { useRenderElement } from '../../utils/useRenderElement' ;
15
+ import { visuallyHidden } from '../../utils/visuallyHidden' ;
14
16
import { warn } from '../../utils/warn' ;
15
17
import { CompositeList , type CompositeMetadata } from '../../composite/list/CompositeList' ;
16
18
import type { FieldRoot } from '../../field/root/FieldRoot' ;
17
19
import { useField } from '../../field/useField' ;
18
20
import { useFieldControlValidation } from '../../field/control/useFieldControlValidation' ;
19
21
import { useFieldRootContext } from '../../field/root/FieldRootContext' ;
22
+ import { useFormContext } from '../../form/FormContext' ;
20
23
import { asc } from '../utils/asc' ;
21
24
import { getSliderValue } from '../utils/getSliderValue' ;
22
25
import { validateMinimumDistance } from '../utils/validateMinimumDistance' ;
23
26
import type { ThumbMetadata } from '../thumb/SliderThumb' ;
24
27
import { sliderStyleHookMapping } from './styleHooks' ;
25
28
import { SliderRootContext } from './SliderRootContext' ;
26
- import { useFormContext } from '../../form/FormContext' ;
27
29
28
30
function areValuesEqual (
29
31
newValue : number | readonly number [ ] ,
@@ -53,6 +55,7 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
53
55
defaultValue,
54
56
disabled : disabledProp = false ,
55
57
id : idProp ,
58
+ inputRef : inputRefProp ,
56
59
format,
57
60
largeStep = 10 ,
58
61
locale,
@@ -93,6 +96,7 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
93
96
94
97
const {
95
98
getValidationProps,
99
+ getInputValidationProps,
96
100
inputRef : inputValidationRef ,
97
101
commitValidation,
98
102
} = useFieldControlValidation ( ) ;
@@ -110,10 +114,12 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
110
114
} ) ;
111
115
112
116
const sliderRef = React . useRef < HTMLElement > ( null ) ;
113
- const controlRef : React . RefObject < HTMLElement | null > = React . useRef ( null ) ;
117
+ const controlRef = React . useRef < HTMLElement > ( null ) ;
118
+ const hiddenInputRef = React . useRef < HTMLInputElement > ( null ) ;
114
119
const thumbRefs = React . useRef < ( HTMLElement | null ) [ ] > ( [ ] ) ;
115
120
const lastChangedValueRef = React . useRef < number | readonly number [ ] | null > ( null ) ;
116
121
const formatOptionsRef = useLatestRef ( format ) ;
122
+ const mergedInputRef = useForkRef ( inputRefProp , hiddenInputRef , inputValidationRef ) ;
117
123
118
124
// We can't use the :active browser pseudo-classes.
119
125
// - The active state isn't triggered when clicking on the rail.
@@ -138,16 +144,6 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
138
144
controlRef,
139
145
} ) ;
140
146
141
- const registerInputValidationRef = React . useCallback (
142
- ( element : HTMLElement | null ) => {
143
- if ( element ) {
144
- controlRef . current = element ;
145
- inputValidationRef . current = element . querySelector < HTMLInputElement > ( 'input[type="range"]' ) ;
146
- }
147
- } ,
148
- [ inputValidationRef ] ,
149
- ) ;
150
-
151
147
const range = Array . isArray ( valueUnwrapped ) ;
152
148
153
149
const values = React . useMemo ( ( ) => {
@@ -272,11 +268,9 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
272
268
max,
273
269
min,
274
270
minStepsBetweenValues,
275
- name,
276
271
onValueCommitted,
277
272
orientation,
278
273
range,
279
- registerInputValidationRef,
280
274
setActive,
281
275
setDragging,
282
276
setValue,
@@ -301,11 +295,9 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
301
295
max ,
302
296
min ,
303
297
minStepsBetweenValues ,
304
- name ,
305
298
onValueCommitted ,
306
299
orientation ,
307
300
range ,
308
- registerInputValidationRef ,
309
301
setActive ,
310
302
setDragging ,
311
303
setValue ,
@@ -317,6 +309,16 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
317
309
] ,
318
310
) ;
319
311
312
+ const serializedValue = React . useMemo ( ( ) => {
313
+ if ( valueUnwrapped == null ) {
314
+ return '' ; // avoid uncontrolled -> controlled error
315
+ }
316
+ if ( ! Array . isArray ( valueUnwrapped ) ) {
317
+ return valueUnwrapped ;
318
+ }
319
+ return JSON . stringify ( valueUnwrapped ) ;
320
+ } , [ valueUnwrapped ] ) ;
321
+
320
322
const renderElement = useRenderElement ( 'div' , componentProps , {
321
323
state,
322
324
ref : [ forwardedRef , sliderRef ] ,
@@ -336,6 +338,16 @@ export const SliderRoot = React.forwardRef(function SliderRoot<
336
338
< SliderRootContext . Provider value = { contextValue } >
337
339
< CompositeList elementsRef = { thumbRefs } onMapChange = { setThumbMap } >
338
340
{ renderElement ( ) }
341
+ < input
342
+ type = "hidden"
343
+ { ...getInputValidationProps ( {
344
+ disabled,
345
+ name,
346
+ ref : mergedInputRef ,
347
+ value : serializedValue ,
348
+ style : visuallyHidden ,
349
+ } ) }
350
+ />
339
351
</ CompositeList >
340
352
</ SliderRootContext . Provider >
341
353
) ;
@@ -401,6 +413,10 @@ export namespace SliderRoot {
401
413
* Options to format the input value.
402
414
*/
403
415
format ?: Intl . NumberFormatOptions ;
416
+ /**
417
+ * A ref to access the hidden input element.
418
+ */
419
+ inputRef ?: React . Ref < HTMLInputElement > ;
404
420
/**
405
421
* The locale used by `Intl.NumberFormat` when formatting the value.
406
422
* Defaults to the user's runtime locale.
0 commit comments