@@ -2,6 +2,7 @@ import { useState, useRef, type ReactNode, useLayoutEffect } from 'react';
2
2
import styled , { css } from 'styled-components' ;
3
3
import { Constants } from '../../../../session' ;
4
4
import { tr } from '../../../../localization/localeTools' ;
5
+ import { useMessagesContainerRef } from '../../../../contexts/MessagesContainerRefContext' ;
5
6
6
7
export const StyledMessageBubble = styled . div < { expanded : boolean } > `
7
8
position: relative;
@@ -40,67 +41,95 @@ const ReadMoreButton = styled.button`
40
41
export function MessageBubble ( { children } : { children : ReactNode } ) {
41
42
const [ expanded , setExpanded ] = useState ( false ) ;
42
43
const [ showReadMore , setShowReadMore ] = useState ( false ) ;
43
- const hiddenHeight = useRef < number > ( 0 ) ;
44
- const containerRef = useRef < HTMLDivElement > ( null ) ;
44
+ const msgBubbleRef = useRef < HTMLDivElement > ( null ) ;
45
+
46
+ const messagesContainerRef = useMessagesContainerRef ( ) ;
47
+
48
+ const scrollBefore = useRef < { scrollTop : number ; scrollHeight : number } > ( {
49
+ scrollTop : 0 ,
50
+ scrollHeight : 0 ,
51
+ } ) ;
45
52
46
53
useLayoutEffect ( ( ) => {
47
54
if ( expanded ) {
48
- // TODO: find the perfect magic number, 1 is almost perfect
49
- // 21 is the ReadMore height, 10 is its vertical padding and 1 is from testing
50
- const scrollDownBy = hiddenHeight . current - 21 - 10 + 1 ;
55
+ const msgContainerAfter = messagesContainerRef . current ;
56
+ if ( ! msgBubbleRef . current || ! msgContainerAfter ) {
57
+ return ;
58
+ }
59
+ const { scrollTop : scrollTopAfter , scrollHeight : scrollHeightAfter } = msgContainerAfter ;
60
+
61
+ const { scrollTop : scrollTopBefore , scrollHeight : scrollHeightBefore } = scrollBefore . current ;
51
62
52
- document . getElementById ( 'messages-container' ) ?. scrollBy ( {
53
- top : - scrollDownBy ,
63
+ const topDidChange = scrollTopAfter !== scrollTopBefore ;
64
+ const heightDiff = scrollHeightAfter - scrollHeightBefore ;
65
+ const scrollTo = topDidChange ? scrollTopBefore - heightDiff : scrollTopAfter - heightDiff ;
66
+
67
+ msgContainerAfter . scrollTo ( {
68
+ top : scrollTo ,
54
69
behavior : 'instant' ,
55
70
} ) ;
56
71
}
57
- } , [ expanded ] ) ;
72
+ } , [ expanded , messagesContainerRef ] ) ;
58
73
59
- useLayoutEffect ( ( ) => {
60
- const container = containerRef . current ;
61
- if ( ! container ) {
74
+ const onShowMore = ( ) => {
75
+ const el = msgBubbleRef . current ;
76
+ if ( ! el ) {
62
77
return ;
63
78
}
64
79
65
- const el = container . firstElementChild ;
66
- if ( ! el ) {
67
- return ;
80
+ const msgContainerBefore = messagesContainerRef . current ;
81
+
82
+ if ( msgContainerBefore ) {
83
+ const { scrollTop : scrollTopBefore , scrollHeight : scrollHeightBefore } = msgContainerBefore ;
84
+
85
+ scrollBefore . current = { scrollTop : scrollTopBefore , scrollHeight : scrollHeightBefore } ;
68
86
}
69
87
70
- // We need the body's child to find the line height as long as it exists
71
- const textEl = el . firstElementChild ?? el ;
72
- const textStyle = window . getComputedStyle ( textEl ) ;
73
- const style = window . getComputedStyle ( el ) ;
88
+ // we cannot "show less", only show more
89
+ setExpanded ( true ) ;
90
+ } ;
74
91
75
- const lineHeight = parseFloat ( textStyle . lineHeight ) ;
76
- const paddingTop = parseFloat ( style . paddingTop ) ;
77
- const paddingBottom = parseFloat ( style . paddingBottom ) ;
78
- const borderTopWidth = parseFloat ( style . borderTopWidth ) ;
79
- const borderBottomWidth = parseFloat ( style . borderBottomWidth ) ;
92
+ useLayoutEffect (
93
+ ( ) => {
94
+ const el = msgBubbleRef ?. current ?. firstElementChild ;
95
+ if ( ! el ) {
96
+ return ;
97
+ }
80
98
81
- // We need to allow for a 1 pixel buffer in maxHeight
82
- const maxHeight =
83
- lineHeight * Constants . CONVERSATION . MAX_MESSAGE_MAX_LINES_BEFORE_READ_MORE + 1 ;
99
+ // We need the body's child to find the line height as long as it exists
100
+ const textEl = el . firstElementChild ?? el ;
101
+ const textStyle = window . getComputedStyle ( textEl ) ;
102
+ const style = window . getComputedStyle ( el ) ;
84
103
85
- const innerHeight =
86
- el . scrollHeight - ( paddingTop + paddingBottom + borderTopWidth + borderBottomWidth ) ;
104
+ const lineHeight = parseFloat ( textStyle . lineHeight ) ;
105
+ const paddingTop = parseFloat ( style . paddingTop ) ;
106
+ const paddingBottom = parseFloat ( style . paddingBottom ) ;
107
+ const borderTopWidth = parseFloat ( style . borderTopWidth ) ;
108
+ const borderBottomWidth = parseFloat ( style . borderBottomWidth ) ;
87
109
88
- const overflowsLines = innerHeight > maxHeight ;
110
+ // We need to allow for a 1 pixel buffer in maxHeight
111
+ const maxHeight =
112
+ lineHeight * Constants . CONVERSATION . MAX_MESSAGE_MAX_LINES_BEFORE_READ_MORE + 1 ;
89
113
90
- hiddenHeight . current = innerHeight - maxHeight ;
91
- setShowReadMore ( overflowsLines ) ;
92
- // eslint-disable-next-line react-hooks/exhaustive-deps -- children changing will change el.lineHeight and el.ScrollHeight
93
- } , [ children ] ) ;
114
+ const innerHeight =
115
+ el . scrollHeight - ( paddingTop + paddingBottom + borderTopWidth + borderBottomWidth ) ;
116
+
117
+ const overflowsLines = innerHeight > maxHeight ;
118
+
119
+ setShowReadMore ( overflowsLines ) ;
120
+ } ,
121
+ // Note: no need to provide a dependency here (and if we provide children, this hook reruns every second for every messages).
122
+ // The only dependency is msgBubbleRef, but as it's a ref it's unneeded
123
+ [ ]
124
+ ) ;
94
125
95
126
return (
96
127
< >
97
- < StyledMessageBubble ref = { containerRef } expanded = { expanded } >
128
+ < StyledMessageBubble ref = { msgBubbleRef } expanded = { expanded } >
98
129
{ children }
99
130
</ StyledMessageBubble >
100
131
{ showReadMore && ! expanded ? (
101
- < ReadMoreButton onClick = { ( ) => setExpanded ( prev => ! prev ) } >
102
- { tr ( 'messageBubbleReadMore' ) }
103
- </ ReadMoreButton >
132
+ < ReadMoreButton onClick = { onShowMore } > { tr ( 'messageBubbleReadMore' ) } </ ReadMoreButton >
104
133
) : null }
105
134
</ >
106
135
) ;
0 commit comments