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