22import * as React from 'react' ;
33import * as ReactDOM from 'react-dom' ;
44import { ownerWindow , ownerDocument } from '@base-ui-components/utils/owner' ;
5- import { isWebKit } from '@base-ui-components/utils/detectBrowser' ;
6- import { useValueAsRef } from '@base-ui-components/utils/useValueAsRef' ;
5+ import { isFirefox , isWebKit } from '@base-ui-components/utils/detectBrowser' ;
76import { useStableCallback } from '@base-ui-components/utils/useStableCallback' ;
7+ import { useTimeout } from '@base-ui-components/utils/useTimeout' ;
88import type { BaseUIComponentProps , HTMLProps } from '../../utils/types' ;
99import { useNumberFieldRootContext } from '../root/NumberFieldRootContext' ;
1010import type { NumberFieldRoot } from '../root/NumberFieldRoot' ;
@@ -43,7 +43,6 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr
4343 setIsScrubbing,
4444 disabled,
4545 readOnly,
46- value,
4746 inputRef,
4847 incrementValue,
4948 getStepAmount,
@@ -52,15 +51,15 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr
5251 valueRef,
5352 } = useNumberFieldRootContext ( ) ;
5453
55- const latestValueRef = useValueAsRef ( value ) ;
56-
5754 const scrubAreaRef = React . useRef < HTMLSpanElement > ( null ) ;
5855
5956 const isScrubbingRef = React . useRef ( false ) ;
6057 const scrubAreaCursorRef = React . useRef < HTMLSpanElement > ( null ) ;
6158 const virtualCursorCoords = React . useRef ( { x : 0 , y : 0 } ) ;
6259 const visualScaleRef = React . useRef ( 1 ) ;
6360
61+ const exitPointerLockTimeout = useTimeout ( ) ;
62+
6463 const [ isTouchInput , setIsTouchInput ] = React . useState ( false ) ;
6564 const [ isPointerLockDenied , setIsPointerLockDenied ] = React . useState ( false ) ;
6665
@@ -146,18 +145,27 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr
146145 let cumulativeDelta = 0 ;
147146
148147 function handleScrubPointerUp ( event : PointerEvent ) {
149- try {
150- // Avoid errors in testing environments.
151- ownerDocument ( scrubAreaRef . current ) . exitPointerLock ( ) ;
152- } catch {
153- //
154- } finally {
155- isScrubbingRef . current = false ;
156- onScrubbingChange ( false , event ) ;
157- onValueCommitted (
158- lastChangedValueRef . current ?? valueRef . current ,
159- createGenericEventDetails ( REASONS . scrub , event ) ,
160- ) ;
148+ function handler ( ) {
149+ try {
150+ ownerDocument ( scrubAreaRef . current ) . exitPointerLock ( ) ;
151+ } catch {
152+ // Ignore errors.
153+ } finally {
154+ isScrubbingRef . current = false ;
155+ onScrubbingChange ( false , event ) ;
156+ onValueCommitted (
157+ lastChangedValueRef . current ?? valueRef . current ,
158+ createGenericEventDetails ( REASONS . scrub , event ) ,
159+ ) ;
160+ }
161+ }
162+
163+ if ( isFirefox ) {
164+ // Firefox needs a small delay here when soft-clicking as the pointer
165+ // lock will not release otherwise.
166+ exitPointerLockTimeout . start ( 20 , handler ) ;
167+ } else {
168+ handler ( ) ;
161169 }
162170 }
163171
@@ -197,16 +205,16 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr
197205 win . addEventListener ( 'pointermove' , handleScrubPointerMove , true ) ;
198206
199207 return ( ) => {
208+ exitPointerLockTimeout . clear ( ) ;
200209 win . removeEventListener ( 'pointerup' , handleScrubPointerUp , true ) ;
201210 win . removeEventListener ( 'pointermove' , handleScrubPointerMove , true ) ;
202211 } ;
203212 } ,
204213 [
205214 disabled ,
206215 readOnly ,
207- isScrubbing ,
208216 incrementValue ,
209- latestValueRef ,
217+ isScrubbing ,
210218 getStepAmount ,
211219 inputRef ,
212220 onScrubbingChange ,
@@ -216,6 +224,7 @@ export const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubAr
216224 lastChangedValueRef ,
217225 onValueCommitted ,
218226 valueRef ,
227+ exitPointerLockTimeout ,
219228 ] ,
220229 ) ;
221230
0 commit comments