@@ -48,7 +48,7 @@ class StartChat extends Component {
4848 this . apiLoaded = false ;
4949 this . customHTMLPriority = false ;
5050
51- this . state = { showBBCode : null , Question :'' , changeLanguage : false , hasBotData : false , textAreaHidden : false , voiceMode : false } ;
51+ this . state = { showBBCode : null , Question :'' , changeLanguage : false , hasBotData : false , textAreaHidden : false , voiceMode : false , messagesReady : false } ;
5252 this . botPayload = null ;
5353 this . handleSubmit = this . handleSubmit . bind ( this ) ;
5454 this . enterKeyDown = this . enterKeyDown . bind ( this ) ;
@@ -329,6 +329,7 @@ class StartChat extends Component {
329329 componentDidMount ( ) {
330330 helperFunctions . prefillFields ( this ) ;
331331 this . updateOnlineFields ( ) ;
332+ this . applyInitialScroll ( ) ;
332333
333334 if ( this . props . botPayload !== null ) {
334335 // This is required because prefill fields can be one of the required
@@ -423,15 +424,20 @@ class StartChat extends Component {
423424 }
424425
425426 // Auto focus if it's show operation
426- if ( ( needFocus === true || ( this . props . chatwidget . get ( 'isMobile' ) == false && prevProps . chatwidget . get ( 'shown' ) === false && this . props . chatwidget . get ( 'shown' ) === true ) ) && this . props . chatwidget . get ( 'mode' ) == 'widget' && this . textMessageRef . current ) {
427- this . textMessageRef . current . focus ( ) ;
428- this . scrollBottom ( ) ;
427+ if ( ( needFocus === true || ( /*this.props.chatwidget.get('isMobile') == false &&*/ prevProps . chatwidget . get ( 'shown' ) === false && this . props . chatwidget . get ( 'shown' ) === true ) ) && this . props . chatwidget . get ( 'mode' ) == 'widget' && this . textMessageRef . current ) {
428+ if ( this . props . chatwidget . get ( 'isMobile' ) == false ) {
429+ this . textMessageRef . current . focus ( ) ;
430+ }
431+ this . scrollToPreviousOrFirst ( ) ;
429432 }
430433
431434 // Rest API data was fetched we can scroll to bottomnow
432435 if ( this . props . chatwidget . getIn ( [ 'onlineData' , 'fetched' ] ) === true && prevProps . chatwidget . getIn ( [ 'onlineData' , 'fetched' ] ) === false ) {
433436 this . props . chatwidget . hasIn ( [ 'chat_ui' , 'uprev' ] ) && helperFunctions . emitEvent ( 'play_sound' , [ { 'type' : 'new_message' , 'sound_on' : ( this . props . chatwidget . getIn ( [ 'usersettings' , 'soundOn' ] ) === true ) , 'widget_open' : ( ( this . props . chatwidget . get ( 'shown' ) && this . props . chatwidget . get ( 'mode' ) == 'widget' ) || document . hasFocus ( ) ) } ] ) ;
434- this . scrollBottom ( ) ;
437+ if ( this . props . chatwidget . get ( 'shown' ) === true ) {
438+ this . scrollToPreviousOrFirst ( ) ;
439+ }
440+
435441 }
436442
437443 // If parent pages changes default department we have to reload
@@ -443,16 +449,55 @@ class StartChat extends Component {
443449 }
444450 this . updateOnlineFieldsInit ( ) ;
445451 }
452+
453+ this . applyInitialScroll ( ) ;
454+ }
455+
456+ // The widget lives inside an iframe which the parent page makes visible before the
457+ // `widgetStatus` event reaches the chat. Our scroll adjustment therefore used to run
458+ // a frame later and the visitor briefly saw the messages at the top before they were
459+ // scrolled to the correct position. Keep them hidden until we positioned them once.
460+ applyInitialScroll ( ) {
461+ if ( this . state . messagesReady === true || ! this . messagesAreaRef . current ) {
462+ return ;
463+ }
464+
465+ // In widget mode the iframe can already be visible before the open event reaches us.
466+ if ( this . props . chatwidget . get ( 'mode' ) == 'widget' && this . props . chatwidget . get ( 'shown' ) !== true ) {
467+ return ;
468+ }
469+
470+ this . scrollToPreviousOrFirst ( ) ;
471+ this . setState ( { messagesReady : true } ) ;
446472 }
447473
448474 scrollBottom ( ) {
449475 if ( this . messagesAreaRef . current ) {
450476 this . messagesAreaRef . current . scrollTop = this . messagesAreaRef . current . scrollHeight + 1000 ;
451- setTimeout ( ( ) => {
452- if ( this . messagesAreaRef . current ) {
453- this . messagesAreaRef . current . scrollTop = this . messagesAreaRef . current . scrollHeight + 1000 ;
454- }
455- } , 450 ) ;
477+ }
478+ }
479+
480+ scrollToPreviousOrFirst ( ) {
481+ const container = this . messagesAreaRef . current ;
482+
483+ if ( ! container ) {
484+ return ;
485+ }
486+
487+ const previousHolder = container . querySelector ( '.previous-holder' ) ;
488+ const targetElement = previousHolder || container . querySelector ( '[id="msg-"]' ) ;
489+
490+ if ( targetElement ) {
491+ // Adjust the scroll position of our own messages container only.
492+ // scrollIntoView() would also scroll every ancestor scroll box (including the
493+ // parent page when the widget is embedded) and respects `scroll-behavior: smooth`,
494+ // which caused a visible flick while the widget was opened for the first time.
495+ // Direct `scrollTop` assignment is always instant and never bubbles to ancestors.
496+ const containerRect = container . getBoundingClientRect ( ) ;
497+ const targetRect = targetElement . getBoundingClientRect ( ) ;
498+ container . scrollTop += ( targetRect . top - containerRect . top ) ;
499+ } else {
500+ this . scrollBottom ( ) ;
456501 }
457502 }
458503
@@ -604,7 +649,9 @@ class StartChat extends Component {
604649 var classMessageInput = ( ! this . props . chatwidget . hasIn ( [ 'chat_ui' , 'bbc_btnh' ] ) || this . props . chatwidget . hasIn ( [ 'chat_ui' , 'lng_btnh' ] ) ? 'ps-0' : 'ps-2' ) + " no-outline form-control rounded-0 form-control rounded-start-0 rounded-end-0 border-0" ;
605650
606651 var msg_expand = "flex-grow-1 overflow-scroll position-relative" ;
607- var bottom_messages = "bottom-message px-1" ;
652+ // Bootstrap `invisible` utility is used to hide the messages until their
653+ // initial scroll position is applied - see applyInitialScroll().
654+ var bottom_messages = "bottom-message px-1" + ( this . state . messagesReady === true ? '' : ' invisible' ) ;
608655
609656 if ( this . props . chatwidget . hasIn ( [ 'chat_ui' , 'msg_expand' ] ) && this . props . chatwidget . get ( 'mode' ) == 'embed' ) {
610657 msg_expand = "overflow-scroll position-relative" ;
0 commit comments