11import { useEffect , useMemo , useRef , useCallback , useState } from "react" ;
22import { useDatamodelView , useDatamodelViewDispatch } from "@/contexts/DatamodelViewContext" ;
33import React from "react" ;
4- import { elementScroll , useVirtualizer , VirtualItem , VirtualizerOptions } from '@tanstack/react-virtual' ;
4+ import { elementScroll , useVirtualizer , VirtualizerOptions } from '@tanstack/react-virtual' ;
55import { Section } from "./Section" ;
66import { useDatamodelData } from "@/contexts/DatamodelDataContext" ;
77import { AttributeType , EntityType , GroupType } from "@/lib/Types" ;
88import { updateURL } from "@/lib/url-utils" ;
99import { copyToClipboard , generateGroupLink } from "@/lib/clipboard-utils" ;
1010import { useSnackbar } from "@/contexts/SnackbarContext" ;
11- import { Tooltip } from '@mui/material' ;
11+ import { debounce , Tooltip } from '@mui/material' ;
1212
1313interface IListProps {
1414}
@@ -106,6 +106,61 @@ export const List = ({ }: IListProps) => {
106106 requestAnimationFrame ( run )
107107 } , [ ] )
108108
109+ const debouncedOnChange = debounce ( ( instance , sync ) => {
110+ if ( ! sync ) {
111+ dispatch ( { type : 'SET_LOADING_SECTION' , payload : null } ) ;
112+ }
113+
114+ const virtualItems = instance . getVirtualItems ( ) ;
115+ if ( virtualItems . length === 0 ) return ;
116+
117+ const scrollOffset = instance . scrollOffset ;
118+ const scrollRect = instance . scrollRect ;
119+ if ( ! scrollOffset || ! scrollRect ) return ;
120+
121+ const viewportTop = scrollOffset ;
122+ const viewportBottom = scrollOffset + scrollRect . height ;
123+
124+ let mostVisibleEntity : {
125+ entity : EntityType ;
126+ group : GroupType ;
127+ visibleArea : number ;
128+ } | null = null ;
129+
130+ for ( const vi of virtualItems ) {
131+ const item = flatItems [ vi . index ] ;
132+ if ( ! item || item . type !== 'entity' ) continue ;
133+
134+ const itemTop = vi . start ;
135+ const itemBottom = vi . end ;
136+
137+ // Calculate intersection
138+ const intersectionTop = Math . max ( itemTop , viewportTop ) ;
139+ const intersectionBottom = Math . min ( itemBottom , viewportBottom ) ;
140+
141+ // Skip if no intersection
142+ if ( intersectionTop >= intersectionBottom ) continue ;
143+
144+ const visibleArea = intersectionBottom - intersectionTop ;
145+
146+ // Update most visible entity without array operations
147+ if ( ! mostVisibleEntity || visibleArea > mostVisibleEntity . visibleArea ) {
148+ mostVisibleEntity = {
149+ entity : item . entity ,
150+ group : item . group ,
151+ visibleArea
152+ } ;
153+ }
154+ }
155+
156+ if ( mostVisibleEntity && currentSection !== mostVisibleEntity . entity . SchemaName ) {
157+ setSectionVirtualItem ( mostVisibleEntity . entity . SchemaName ) ;
158+ updateURL ( { query : { group : mostVisibleEntity . group . Name , section : mostVisibleEntity . entity . SchemaName } } ) ;
159+ dispatch ( { type : "SET_CURRENT_GROUP" , payload : mostVisibleEntity . group . Name } ) ;
160+ dispatch ( { type : "SET_CURRENT_SECTION" , payload : mostVisibleEntity . entity . SchemaName } ) ;
161+ }
162+ } , 100 ) ;
163+
109164 const rowVirtualizer = useVirtualizer ( {
110165 count : flatItems . length ,
111166 getScrollElement : ( ) => parentRef . current ,
@@ -116,49 +171,7 @@ export const List = ({ }: IListProps) => {
116171 return item . type === 'group' ? 100 : 500 ;
117172 } ,
118173 scrollToFn,
119- onChange : ( instance , sync ) => {
120- // Only update during actual scrolling (sync = true)
121- if ( ! sync ) {
122- dispatch ( { type : 'SET_LOADING_SECTION' , payload : null } ) ;
123- return ;
124- }
125-
126- const virtualItems = instance . getVirtualItems ( ) ;
127- if ( virtualItems . length === 0 ) return ;
128-
129- const scrollOffset = instance . scrollOffset ;
130- const scrollRect = instance . scrollRect ;
131- if ( ! scrollOffset || ! scrollRect ) return ;
132-
133- // Find the first entity item that's currently visible
134- const firstVisibleEntity = virtualItems . find ( vi => {
135- const item = flatItems [ vi . index ] ;
136- if ( ! item || item . type !== 'entity' ) return false ;
137-
138- // Check if this virtual item is actually visible in the viewport
139- // vi.start is the top position of the item, vi.end would be vi.start + vi.size
140- const itemTop = vi . start ;
141- const itemBottom = vi . end ;
142- const offset = 80 ;
143-
144- // An item is visible if its bottom is below the scroll position
145- // and its top is above the scroll position + viewport height
146- return itemBottom - offset > scrollOffset && itemTop < scrollOffset + scrollRect . height ;
147- } ) ;
148-
149- if ( firstVisibleEntity ) {
150- const item = flatItems [ firstVisibleEntity . index ] ;
151- if ( item && item . type === 'entity' ) {
152- // Only update if the section has actually changed
153- if ( currentSection !== item . entity . SchemaName ) {
154- setSectionVirtualItem ( item . entity . SchemaName ) ;
155- updateURL ( { query : { group : item . group . Name , section : item . entity . SchemaName } } ) ;
156- dispatch ( { type : "SET_CURRENT_GROUP" , payload : item . group . Name } ) ;
157- dispatch ( { type : "SET_CURRENT_SECTION" , payload : item . entity . SchemaName } ) ;
158- }
159- }
160- }
161- } ,
174+ onChange : debouncedOnChange ,
162175 } ) ;
163176
164177 const scrollToSection = useCallback ( ( sectionId : string ) => {
@@ -201,7 +214,6 @@ export const List = ({ }: IListProps) => {
201214 } , [ flatItems ] ) ;
202215
203216 const restoreSection = useCallback ( ( ) => {
204- console . log ( sectionVirtualItem )
205217 if ( sectionVirtualItem ) {
206218 scrollToSection ( sectionVirtualItem ) ;
207219 }
0 commit comments