11import type { VirtualItem } from '@tanstack/react-virtual' ;
2+ import { useIsomorphicLayoutEffect } from '@ui5/webcomponents-react-base' ;
3+ import { useRef } from 'react' ;
24import 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' ;
56
6- interface RowSubComponent {
7- subComponentsHeight : Record < string , { rowId : string ; subComponentHeight ?: number } > ;
7+ interface RowSubComponentProps {
8+ subComponentsHeight : Record < string , { rowId : string ; subComponentHeight ?: number } > | undefined ;
89 virtualRow : VirtualItem ;
910 dispatch : ( e : { type : string ; payload ?: Record < string , unknown > } ) => void ;
10- row : Record < string , unknown > ;
11+ row : RowType ;
1112 rowHeight : number ;
1213 children : ReactNode | ReactNode [ ] ;
13- rows : Record < string , unknown > [ ] ;
14+ rows : RowType [ ] ;
1415 alwaysShowSubComponent : boolean ;
1516 rowIndex : number ;
1617 classNames : ClassNames ;
18+ renderSubComp : boolean ;
1719}
1820
19- export const RowSubComponent = ( props : RowSubComponent ) => {
21+ export function RowSubComponent ( props : RowSubComponentProps ) {
2022 const {
21- subComponentsHeight,
23+ subComponentsHeight = { } ,
2224 virtualRow,
2325 dispatch,
2426 row,
@@ -28,79 +30,76 @@ export const RowSubComponent = (props: RowSubComponent) => {
2830 alwaysShowSubComponent,
2931 rowIndex,
3032 classNames,
33+ renderSubComp,
3134 } = props ;
32- const subComponentRef = useRef ( null ) ;
35+ const subComponentRef = useRef < HTMLDivElement > ( null ) ;
3336
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+ }
5342
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 ;
8756 }
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 ) ;
9079 } ) ;
91- if ( subComponentRef . current ?. firstChild ) {
92- subComponentHeightObserver . observe ( subComponentRef . current ?. firstChild ) ;
93- }
80+ observer . observe ( subComponentElement ) ;
81+
9482 return ( ) => {
95- subComponentHeightObserver . disconnect ( ) ;
83+ observer . disconnect ( ) ;
9684 } ;
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+ }
104103
105104 return (
106105 < div
@@ -117,6 +116,6 @@ export const RowSubComponent = (props: RowSubComponent) => {
117116 { children }
118117 </ div >
119118 ) ;
120- } ;
119+ }
121120
122121RowSubComponent . displayName = 'RowSubComponent' ;
0 commit comments