@@ -35,7 +35,11 @@ const STRIP_CLASS = 'tw-flex tw-flex-col tw-items-end tw-gap-2 tw-py-1 tw-pr-1';
3535const PANEL_LIST_CLASS = 'tw-flex tw-max-h-[60vh] tw-flex-col tw-gap-1 tw-overflow-auto' ;
3636const OUTLINE_REBUILD_SETTLE_MS = 180 ;
3737
38- function readViewportRect ( ) : ReaderOutlineCandidate [ 'rect' ] {
38+ function readViewportRect ( scrollRoot ?: Element | null ) : ReaderOutlineCandidate [ 'rect' ] {
39+ if ( scrollRoot && typeof scrollRoot . getBoundingClientRect === 'function' ) {
40+ const rect = scrollRoot . getBoundingClientRect ( ) ;
41+ return { top : rect . top , bottom : rect . bottom } ;
42+ }
3943 const view = globalThis . window ?? null ;
4044 const height = Number ( view ?. innerHeight ) ;
4145 if ( ! Number . isFinite ( height ) || height <= 0 ) return { top : 0 , bottom : 0 } ;
@@ -121,28 +125,34 @@ function renderOutlineItem(
121125 ) ;
122126}
123127
124- export function useArticleOutlineMinimap ( root : HTMLElement | null ) : ArticleOutlineMinimapState {
128+ export function useArticleOutlineMinimap (
129+ root : HTMLElement | null ,
130+ scrollRoot ?: Element | null ,
131+ ) : ArticleOutlineMinimapState {
125132 const [ entries , setEntries ] = useState < ReaderOutlineDomEntry [ ] > ( [ ] ) ;
126133 const [ activeIndex , setActiveIndex ] = useState < number | null > ( null ) ;
127134 const entriesRef = useRef < ReaderOutlineDomEntry [ ] > ( [ ] ) ;
128135 const activeIndexRef = useRef < number | null > ( null ) ;
129136 const rafRef = useRef < number | null > ( null ) ;
130137 const pendingKindRef = useRef < 'entries' | 'active' | null > ( null ) ;
131138
132- const syncActiveIndex = useCallback ( ( currentEntries : ReaderOutlineDomEntry [ ] = entriesRef . current ) => {
133- const candidates = readCurrentCandidates ( currentEntries ) ;
134- const nextActiveIndex = candidates . length
135- ? pickReaderOutlineActiveIndex ( { viewportRect : readViewportRect ( ) , candidates } )
136- : null ;
137- publishReaderPerformanceStats ( ( current ) => ( {
138- ...current ,
139- outlineEntries : currentEntries . length ,
140- outlineActiveRecalcCount : current . outlineActiveRecalcCount + 1 ,
141- } ) ) ;
142- if ( nextActiveIndex === activeIndexRef . current ) return ;
143- activeIndexRef . current = nextActiveIndex ;
144- setActiveIndex ( nextActiveIndex ) ;
145- } , [ ] ) ;
139+ const syncActiveIndex = useCallback (
140+ ( currentEntries : ReaderOutlineDomEntry [ ] = entriesRef . current ) => {
141+ const candidates = readCurrentCandidates ( currentEntries ) ;
142+ const nextActiveIndex = candidates . length
143+ ? pickReaderOutlineActiveIndex ( { viewportRect : readViewportRect ( scrollRoot ) , candidates } )
144+ : null ;
145+ publishReaderPerformanceStats ( ( current ) => ( {
146+ ...current ,
147+ outlineEntries : currentEntries . length ,
148+ outlineActiveRecalcCount : current . outlineActiveRecalcCount + 1 ,
149+ } ) ) ;
150+ if ( nextActiveIndex === activeIndexRef . current ) return ;
151+ activeIndexRef . current = nextActiveIndex ;
152+ setActiveIndex ( nextActiveIndex ) ;
153+ } ,
154+ [ scrollRoot ] ,
155+ ) ;
146156
147157 const rebuild = useCallback ( ( ) => {
148158 if ( ! root ) {
@@ -231,7 +241,7 @@ export function useArticleOutlineMinimap(root: HTMLElement | null): ArticleOutli
231241 observer . observe ( root , { childList : true , subtree : true , characterData : true } ) ;
232242 }
233243
234- const scrollTarget : EventTarget = win || root ;
244+ const scrollTarget : EventTarget = scrollRoot ?? win ?? root ;
235245 scrollTarget . addEventListener ?.( 'scroll' , onScroll , { passive : true } ) ;
236246 win ?. addEventListener ?.( 'resize' , onResize , { passive : true } ) ;
237247 schedule ( 'entries' ) ;
@@ -248,7 +258,7 @@ export function useArticleOutlineMinimap(root: HTMLElement | null): ArticleOutli
248258 }
249259 pendingKindRef . current = null ;
250260 } ;
251- } , [ rebuild , root , syncActiveIndex ] ) ;
261+ } , [ rebuild , root , scrollRoot , syncActiveIndex ] ) ;
252262
253263 return { entries, activeIndex } ;
254264}
0 commit comments