1- import { isElementType } from '../utils'
1+ import { getWindow , isElementType } from '../utils'
22import { prepareInterceptor } from './interceptor'
3- import { setUISelection } from './selection'
3+ import { hasUISelection , setUISelection } from './selection'
44
55const UIValue = Symbol ( 'Displayed value in UI' )
66const InitialValue = Symbol ( 'Initial value to compare on blur' )
@@ -12,6 +12,9 @@ type Value = {
1212}
1313
1414declare global {
15+ interface Window {
16+ REACT_VERSION ?: number
17+ }
1518 interface Element {
1619 [ UIValue ] ?: string
1720 [ InitialValue ] ?: string
@@ -31,7 +34,7 @@ function valueInterceptor(
3134
3235 if ( isUI ) {
3336 this [ UIValue ] = String ( v )
34- setPreviousValue ( this , String ( this . value ) )
37+ startTrackValue ( this )
3538 }
3639
3740 return {
@@ -102,19 +105,28 @@ export function getInitialValue(
102105 return element [ InitialValue ]
103106}
104107
105- function setPreviousValue (
106- element : HTMLInputElement | HTMLTextAreaElement ,
107- v : string ,
108- ) {
109- element [ TrackChanges ] = { ...element [ TrackChanges ] , previousValue : v }
108+ // When the input event happens in the browser, React executes all event handlers
109+ // and if they change state of a controlled value, nothing happens.
110+ // But when we trigger the event handlers in test environment with React@17,
111+ // the changes are rolled back before the state update is applied.
112+ // This results in a reset cursor.
113+ // There might be a better way to work around if we figure out
114+ // why the batched update is executed differently in our test environment.
115+
116+ function isReact17Element ( element : Element ) {
117+ return (
118+ Object . getOwnPropertyNames ( element ) . some ( k => k . startsWith ( '__react' ) ) &&
119+ getWindow ( element ) . REACT_VERSION === 17
120+ )
110121}
111122
112- export function startTrackValue (
113- element : HTMLInputElement | HTMLTextAreaElement ,
114- ) {
123+ function startTrackValue ( element : HTMLInputElement | HTMLTextAreaElement ) {
124+ if ( ! isReact17Element ( element ) ) {
125+ return
126+ }
127+
115128 element [ TrackChanges ] = {
116- ...element [ TrackChanges ] ,
117- nextValue : String ( element . value ) ,
129+ previousValue : String ( element . value ) ,
118130 tracked : [ ] ,
119131 }
120132}
@@ -125,38 +137,36 @@ function trackOrSetValue(
125137) {
126138 element [ TrackChanges ] ?. tracked ?. push ( v )
127139
128- if ( ! element [ TrackChanges ] ?. tracked ) {
129- setCleanValue ( element , v )
140+ if ( ! element [ TrackChanges ] ) {
141+ setUIValueClean ( element )
142+ setUISelection ( element , { focusOffset : v . length } )
130143 }
131144}
132145
133- function setCleanValue (
146+ export function commitValueAfterInput (
134147 element : HTMLInputElement | HTMLTextAreaElement ,
135- v : string ,
148+ cursorOffset : number ,
136149) {
137- element [ UIValue ] = undefined
138-
139- // Programmatically setting the value property
140- // moves the cursor to the end of the input.
141- setUISelection ( element , { focusOffset : v . length } )
142- }
143-
144- /**
145- * @returns `true` if we recognize a React state reset and update
146- */
147- export function endTrackValue ( element : HTMLInputElement | HTMLTextAreaElement ) {
148150 const changes = element [ TrackChanges ]
149151
150152 element [ TrackChanges ] = undefined
151153
154+ if ( ! changes ?. tracked ?. length ) {
155+ return
156+ }
157+
152158 const isJustReactStateUpdate =
153- changes ? .tracked ? .length === 2 &&
159+ changes . tracked . length === 2 &&
154160 changes . tracked [ 0 ] === changes . previousValue &&
155- changes . tracked [ 1 ] === changes . nextValue
161+ changes . tracked [ 1 ] === element . value
156162
157- if ( changes ?. tracked ?. length && ! isJustReactStateUpdate ) {
158- setCleanValue ( element , changes . tracked [ changes . tracked . length - 1 ] )
163+ if ( ! isJustReactStateUpdate ) {
164+ setUIValueClean ( element )
159165 }
160166
161- return isJustReactStateUpdate
167+ if ( hasUISelection ( element ) ) {
168+ setUISelection ( element , {
169+ focusOffset : isJustReactStateUpdate ? cursorOffset : element . value . length ,
170+ } )
171+ }
162172}
0 commit comments