@@ -11,7 +11,14 @@ import type {Element, SuspenseNode} from '../../../frontend/types';
1111import type Store from '../../store' ;
1212
1313import * as React from 'react' ;
14- import { useContext , useLayoutEffect , useMemo , useRef } from 'react' ;
14+ import {
15+ useContext ,
16+ useId ,
17+ useLayoutEffect ,
18+ useMemo ,
19+ useRef ,
20+ useState ,
21+ } from 'react' ;
1522import { BridgeContext , StoreContext } from '../context' ;
1623import { TreeDispatcherContext } from '../Components/TreeContext' ;
1724import { useHighlightHostInstance } from '../hooks' ;
@@ -87,17 +94,47 @@ export default function SuspenseTimeline(): React$Node {
8794 } , [ ] ) ;
8895
8996 const min = 0 ;
90- const max = timeline . length - 1 ;
97+ const max = timeline . length > 0 ? timeline . length - 1 : 0 ;
98+
99+ const [ value , setValue ] = useState ( max ) ;
100+ if ( value > max ) {
101+ // TODO: Handle timeline changes
102+ setValue ( max ) ;
103+ }
104+
105+ const markersID = useId ( ) ;
106+ const markers : React . Node [ ] = useMemo ( ( ) => {
107+ return timeline . map ( ( suspense , index ) => {
108+ const takesUpSpace =
109+ suspense . rects !== null &&
110+ suspense . rects . some ( rect => {
111+ return rect . width > 0 && rect . height > 0 ;
112+ } ) ;
113+
114+ return takesUpSpace ? (
115+ < option
116+ key = { suspense . id }
117+ className = {
118+ index === value ? styles . SuspenseTimelineActiveMarker : undefined
119+ }
120+ value = { index } >
121+ #{ index + 1 }
122+ </ option >
123+ ) : (
124+ < option key = { suspense . id } />
125+ ) ;
126+ } ) ;
127+ } , [ timeline , value ] ) ;
91128
92129 function handleChange ( event : SyntheticEvent < HTMLInputElement > ) {
93- const value = + event . currentTarget . value ;
130+ const pendingValue = + event . currentTarget . value ;
94131 for ( let i = 0 ; i < timeline . length ; i ++ ) {
95- const forceFallback = i > value ;
132+ const forceFallback = i > pendingValue ;
96133 const suspense = timeline [ i ] ;
97134 const elementID = suspense . id ;
98135 const rendererID = store . getRendererIDForElement ( elementID ) ;
99136 if ( rendererID === null ) {
100- // TODO: This sounds like a bug .
137+ // TODO: Handle disconnected elements .
101138 console . warn (
102139 `No renderer ID found for element ${ elementID } in suspense timeline.` ,
103140 ) ;
@@ -110,18 +147,18 @@ export default function SuspenseTimeline(): React$Node {
110147 }
111148 }
112149
113- const suspense = timeline [ value ] ;
150+ const suspense = timeline [ pendingValue ] ;
114151 const elementID = suspense . id ;
115152 highlightHostInstance ( elementID ) ;
116153 dispatch ( { type : 'SELECT_ELEMENT_BY_ID' , payload : elementID } ) ;
154+ setValue ( pendingValue ) ;
117155 }
118156
119157 function handleBlur ( ) {
120158 clearHighlightHostInstance ( ) ;
121159 }
122160
123- function handleFocus ( event : SyntheticEvent < HTMLInputElement > ) {
124- const value = + event . currentTarget . value ;
161+ function handleFocus ( ) {
125162 const suspense = timeline [ value ] ;
126163
127164 highlightHostInstance ( suspense . id ) ;
@@ -133,7 +170,7 @@ export default function SuspenseTimeline(): React$Node {
133170 throw new Error ( 'Bounding box of slider is unknown.' ) ;
134171 }
135172
136- const value = Math . max (
173+ const hoveredValue = Math . max (
137174 min ,
138175 Math . min (
139176 Math . round (
@@ -142,10 +179,10 @@ export default function SuspenseTimeline(): React$Node {
142179 max ,
143180 ) ,
144181 ) ;
145- const suspense = timeline [ value ] ;
182+ const suspense = timeline [ hoveredValue ] ;
146183 if ( suspense === undefined ) {
147184 throw new Error (
148- `Suspense node not found for value ${ value } in timeline when on ${ event . clientX } in bounding box ${ JSON . stringify ( bbox ) } .` ,
185+ `Suspense node not found for value ${ hoveredValue } in timeline when on ${ event . clientX } in bounding box ${ JSON . stringify ( bbox ) } .` ,
149186 ) ;
150187 }
151188 highlightHostInstance ( suspense . id ) ;
@@ -158,14 +195,18 @@ export default function SuspenseTimeline(): React$Node {
158195 type = "range"
159196 min = { min }
160197 max = { max }
161- defaultValue = { max }
198+ list = { markersID }
199+ value = { value }
162200 onBlur = { handleBlur }
163201 onChange = { handleChange }
164202 onFocus = { handleFocus }
165203 onPointerMove = { handlePointerMove }
166204 onPointerUp = { clearHighlightHostInstance }
167205 ref = { inputRef }
168206 />
207+ < datalist id = { markersID } className = { styles . SuspenseTimelineMarkers } >
208+ { markers }
209+ </ datalist >
169210 </ div >
170211 ) ;
171212}
0 commit comments