@@ -60,6 +60,8 @@ export default class Composer extends React.Component {
6060 scrollToTop : ( ) => this . state . functionContext . scrollTo ( 0 )
6161 } ,
6262 internalContext : {
63+ offsetHeight : 0 ,
64+ scrollHeight : 0 ,
6365 setTarget : target => this . setState ( ( ) => ( { target } ) )
6466 } ,
6567 scrollTop : props . mode === 'top' ? 0 : '100%' ,
@@ -151,29 +153,51 @@ export default class Composer extends React.Component {
151153 const { target } = state ;
152154
153155 if ( target ) {
154- const { scrollTop, stateContext } = state ;
156+ const { internalContext , scrollTop, stateContext } = state ;
155157 const { atBottom, atEnd, atStart, atTop } = computeViewState ( state ) ;
158+ let nextInternalContext = internalContext ;
156159 let nextStateContext = stateContext ;
157160
158161 nextStateContext = updateIn ( nextStateContext , [ 'atBottom' ] , ( ) => atBottom ) ;
159162 nextStateContext = updateIn ( nextStateContext , [ 'atEnd' ] , ( ) => atEnd ) ;
160163 nextStateContext = updateIn ( nextStateContext , [ 'atStart' ] , ( ) => atStart ) ;
161164 nextStateContext = updateIn ( nextStateContext , [ 'atTop' ] , ( ) => atTop ) ;
162165
166+ // Chrome will emit "synthetic" scroll event if the container is resized or an element is added
167+ // We need to ignore these "synthetic" events
168+ // Repro: In playground, press 4-1-5-1-1 (small, add one, normal, add one, add one)
169+ // Nomatter how fast or slow the sequence is being presssed, it should still stick to the bottom
170+ const { offsetHeight, scrollHeight } = target ;
171+ const resized = offsetHeight !== internalContext . offsetHeight ;
172+ const elementChanged = scrollHeight !== internalContext . scrollHeight ;
173+
174+ if ( resized ) {
175+ nextInternalContext = updateIn ( nextInternalContext , [ 'offsetHeight' ] , ( ) => offsetHeight ) ;
176+ }
177+
178+ if ( elementChanged ) {
179+ nextInternalContext = updateIn ( nextInternalContext , [ 'scrollHeight' ] , ( ) => scrollHeight ) ;
180+ }
181+
163182 // Sticky means:
164183 // - If it is scrolled programatically, we are still in sticky mode
165184 // - If it is scrolled by the user, then sticky means if we are at the end
166- nextStateContext = updateIn ( nextStateContext , [ 'sticky' ] , ( ) => stateContext . animating ? true : atEnd ) ;
185+
186+ // Only update stickiness if the scroll event is not due to synthetic scroll done by Chrome
187+ if ( ! resized && ! elementChanged ) {
188+ nextStateContext = updateIn ( nextStateContext , [ 'sticky' ] , ( ) => stateContext . animating ? true : atEnd ) ;
189+ }
167190
168191 // If no scrollTop is set (not in programmatic scrolling mode), we should set "animating" to false
169192 // "animating" is used to calculate the "sticky" property
170193 if ( scrollTop === null ) {
171194 nextStateContext = updateIn ( nextStateContext , [ 'animating' ] , ( ) => false ) ;
172195 }
173196
174- if ( stateContext !== nextStateContext ) {
175- return { stateContext : nextStateContext } ;
176- }
197+ return {
198+ ...internalContext === nextInternalContext ? { } : { internalContext : nextInternalContext } ,
199+ ...stateContext === nextStateContext ? { } : { stateContext : nextStateContext }
200+ } ;
177201 }
178202 } , ( ) => {
179203 this . state . stateContext . sticky && this . enableWorker ( ) ;
0 commit comments