1
1
import type { VirtualItem } from '@tanstack/react-virtual' ;
2
+ import { useIsomorphicLayoutEffect } from '@ui5/webcomponents-react-base' ;
3
+ import { useRef } from 'react' ;
2
4
import type { ReactNode } from 'react' ;
3
- import { useEffect , useRef } from 'react' ;
4
- import type { ClassNames } from '../types/index.js' ;
5
+ import type { ClassNames , RowType } from '../types/index.js' ;
5
6
6
- interface RowSubComponent {
7
- subComponentsHeight : Record < string , { rowId : string ; subComponentHeight ?: number } > ;
7
+ interface RowSubComponentProps {
8
+ subComponentsHeight : Record < string , { rowId : string ; subComponentHeight ?: number } > | undefined ;
8
9
virtualRow : VirtualItem ;
9
10
dispatch : ( e : { type : string ; payload ?: Record < string , unknown > } ) => void ;
10
- row : Record < string , unknown > ;
11
+ row : RowType ;
11
12
rowHeight : number ;
12
13
children : ReactNode | ReactNode [ ] ;
13
- rows : Record < string , unknown > [ ] ;
14
+ rows : RowType [ ] ;
14
15
alwaysShowSubComponent : boolean ;
15
16
rowIndex : number ;
16
17
classNames : ClassNames ;
18
+ renderSubComp : boolean ;
17
19
}
18
20
19
- export const RowSubComponent = ( props : RowSubComponent ) => {
21
+ export function RowSubComponent ( props : RowSubComponentProps ) {
20
22
const {
21
- subComponentsHeight,
23
+ subComponentsHeight = { } ,
22
24
virtualRow,
23
25
dispatch,
24
26
row,
@@ -28,79 +30,76 @@ export const RowSubComponent = (props: RowSubComponent) => {
28
30
alwaysShowSubComponent,
29
31
rowIndex,
30
32
classNames,
33
+ renderSubComp,
31
34
} = props ;
32
- const subComponentRef = useRef ( null ) ;
35
+ const subComponentRef = useRef < HTMLDivElement > ( null ) ;
33
36
34
- useEffect ( ( ) => {
35
- const subComponentHeightObserver = new ResizeObserver ( ( entries ) => {
36
- entries . forEach ( ( entry ) => {
37
- const target = entry . target . getBoundingClientRect ( ) ;
38
- if ( target ) {
39
- // Firefox implements `borderBoxSize` as a single content rect, rather than an array
40
- const borderBoxSize = Array . isArray ( entry . borderBoxSize ) ? entry . borderBoxSize [ 0 ] : entry . borderBoxSize ;
41
- // Safari doesn't implement `borderBoxSize`
42
- const subCompHeight = borderBoxSize ?. blockSize ?? target . height ;
43
- if ( subComponentsHeight ?. [ virtualRow . index ] ?. subComponentHeight !== subCompHeight && subCompHeight !== 0 ) {
44
- // use most common sub-component height of first 10 sub-components as default height
45
- if ( alwaysShowSubComponent && subComponentsHeight && Object . keys ( subComponentsHeight ) . length === 10 ) {
46
- const objGroupedByHeight = Object . values ( subComponentsHeight ) . reduce ( ( acc , cur ) => {
47
- const count = acc ?. [ cur . subComponentHeight ] ;
48
- if ( typeof count === 'number' ) {
49
- return { ...acc , [ cur . subComponentHeight ] : count + 1 } ;
50
- }
51
- return { ...acc , [ cur . subComponentHeight ] : 1 } ;
52
- } , { } ) ;
37
+ useIsomorphicLayoutEffect ( ( ) => {
38
+ const subComponentElement = subComponentRef . current ;
39
+ if ( ! subComponentElement || ! renderSubComp ) {
40
+ return ;
41
+ }
53
42
54
- const mostUsedHeight = Object . keys ( objGroupedByHeight ) . reduce ( ( a , b ) =>
55
- objGroupedByHeight [ a ] > objGroupedByHeight [ b ] ? a : b ,
56
- ) ;
57
- const estimatedHeights = rows . reduce ( ( acc , cur , index ) => {
58
- acc [ index ] = { subComponentHeight : parseInt ( mostUsedHeight ) , rowId : cur . id } ;
59
- return acc ;
60
- } , { } ) ;
61
- dispatch ( {
62
- type : 'SUB_COMPONENTS_HEIGHT' ,
63
- payload : { ...estimatedHeights , ...subComponentsHeight } ,
64
- } ) ;
65
- } else {
66
- dispatch ( {
67
- type : 'SUB_COMPONENTS_HEIGHT' ,
68
- payload : {
69
- ...subComponentsHeight ,
70
- [ virtualRow . index ] : { subComponentHeight : subCompHeight , rowId : row . id } ,
71
- } ,
72
- } ) ;
73
- }
74
- }
75
- // recalc if row id of row index has changed
76
- if (
77
- subComponentsHeight ?. [ virtualRow . index ] ?. rowId != null &&
78
- subComponentsHeight ?. [ virtualRow . index ] ?. rowId !== row . id
79
- ) {
80
- dispatch ( {
81
- type : 'SUB_COMPONENTS_HEIGHT' ,
82
- payload : {
83
- ...subComponentsHeight ,
84
- [ virtualRow . index ] : { subComponentHeight : subCompHeight , rowId : row . id } ,
85
- } ,
86
- } ) ;
43
+ const measureAndDispatch = ( height : number ) => {
44
+ const prev : { rowId ?: string ; subComponentHeight ?: number } = subComponentsHeight ?. [ virtualRow . index ] ?? { } ;
45
+ if ( height === 0 || ( prev . subComponentHeight === height && prev . rowId === row . id ) ) {
46
+ return ;
47
+ }
48
+
49
+ // use most common subComponentHeight height of first 10 subcomponents as default height
50
+ if ( alwaysShowSubComponent && Object . keys ( subComponentsHeight ) . length === 10 ) {
51
+ // create frequency map -> most common height has the highest number
52
+ const frequencyMap : Record < number , number > = { } ;
53
+ Object . values ( subComponentsHeight ) . forEach ( ( { subComponentHeight } ) => {
54
+ if ( subComponentHeight ) {
55
+ frequencyMap [ subComponentHeight ] = ( frequencyMap [ subComponentHeight ] || 0 ) + 1 ;
87
56
}
88
- }
89
- } ) ;
57
+ } ) ;
58
+ const mostUsedHeight = Number ( Object . entries ( frequencyMap ) . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) [ 0 ] ?. [ 0 ] || 0 ) ;
59
+ const estimatedHeight : typeof subComponentsHeight = { } ;
60
+ rows . forEach ( ( row , index ) => {
61
+ estimatedHeight [ index ] = { subComponentHeight : mostUsedHeight , rowId : row . id } ;
62
+ } ) ;
63
+ dispatch ( { type : 'SUB_COMPONENTS_HEIGHT' , payload : { ...estimatedHeight , ...subComponentsHeight } } ) ;
64
+ } else {
65
+ dispatch ( {
66
+ type : 'SUB_COMPONENTS_HEIGHT' ,
67
+ payload : {
68
+ ...subComponentsHeight ,
69
+ [ virtualRow . index ] : { subComponentHeight : height , rowId : row . id } ,
70
+ } ,
71
+ } ) ;
72
+ }
73
+ } ;
74
+
75
+ measureAndDispatch ( subComponentElement . getBoundingClientRect ( ) . height ) ;
76
+
77
+ const observer = new ResizeObserver ( ( [ entry ] ) => {
78
+ measureAndDispatch ( entry . borderBoxSize [ 0 ] . blockSize ) ;
90
79
} ) ;
91
- if ( subComponentRef . current ?. firstChild ) {
92
- subComponentHeightObserver . observe ( subComponentRef . current ?. firstChild ) ;
93
- }
80
+ observer . observe ( subComponentElement ) ;
81
+
94
82
return ( ) => {
95
- subComponentHeightObserver . disconnect ( ) ;
83
+ observer . disconnect ( ) ;
96
84
} ;
97
- } , [
98
- subComponentRef . current ?. firstChild ,
99
- subComponentsHeight ,
100
- row . id ,
101
- subComponentsHeight ?. [ virtualRow . index ] ?. subComponentHeight ,
102
- virtualRow . index ,
103
- ] ) ;
85
+ } , [ renderSubComp , subComponentsHeight , virtualRow . index , row . id , alwaysShowSubComponent , rows ] ) ;
86
+
87
+ // reset subComponentHeight
88
+ useIsomorphicLayoutEffect ( ( ) => {
89
+ if ( ! renderSubComp && subComponentsHeight ?. [ virtualRow . index ] ?. subComponentHeight ) {
90
+ dispatch ( {
91
+ type : 'SUB_COMPONENTS_HEIGHT' ,
92
+ payload : {
93
+ ...subComponentsHeight ,
94
+ [ virtualRow . index ] : { subComponentHeight : 0 , rowId : row . id } ,
95
+ } ,
96
+ } ) ;
97
+ }
98
+ } , [ renderSubComp , subComponentsHeight , virtualRow . index , row . id , dispatch ] ) ;
99
+
100
+ if ( ! renderSubComp ) {
101
+ return null ;
102
+ }
104
103
105
104
return (
106
105
< div
@@ -117,6 +116,6 @@ export const RowSubComponent = (props: RowSubComponent) => {
117
116
{ children }
118
117
</ div >
119
118
) ;
120
- } ;
119
+ }
121
120
122
121
RowSubComponent . displayName = 'RowSubComponent' ;
0 commit comments