Skip to content

Commit 408596e

Browse files
committed
fix: add precalculated measurement
1 parent 6a8bbcc commit 408596e

File tree

1 file changed

+124
-5
lines changed

1 file changed

+124
-5
lines changed

packages/webui/src/client/lib/VirtualElement.tsx

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'
22
import { InView } from 'react-intersection-observer'
33
import { getViewPortScrollingState } from './viewPort'
44

5+
interface IElementMeasurements {
6+
width: string | number
7+
clientHeight: number
8+
marginLeft: string | number | undefined
9+
marginRight: string | number | undefined
10+
marginTop: string | number | undefined
11+
marginBottom: string | number | undefined
12+
id: string | undefined
13+
}
14+
515
const IDLE_CALLBACK_TIMEOUT = 100
616
// Increased delay for Safari, as Safari doesn't have scroll time when using 'smooth' scroll
717
const SAFARI_VISIBILITY_DELAY = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) ? 100 : 0
@@ -52,9 +62,15 @@ export function VirtualElement({
5262
id?: string | undefined
5363
className?: string
5464
}>): JSX.Element | null {
65+
const [waitForInitialLoad, setWaitForInitialLoad] = useState(true)
5566
const [inView, setInView] = useState(initialShow ?? false)
5667
const [isShowingChildren, setIsShowingChildren] = useState(inView)
5768

69+
const [measurements, setMeasurements] = useState<IElementMeasurements | null>(null)
70+
const [ref, setRef] = useState<HTMLDivElement | null>(null)
71+
72+
const showPlaceholder = !isShowingChildren && !initialShow
73+
5874
// Track the last visibility change to debounce
5975
const lastVisibilityChangeRef = useRef<number>(0)
6076

@@ -107,6 +123,21 @@ export function VirtualElement({
107123
useEffect(() => {
108124
if (inView === true) {
109125
setIsShowingChildren(true)
126+
127+
// Schedule a measurement after a short delay
128+
if (waitForInitialLoad && ref) {
129+
const initialMeasurementTimeout = window.setTimeout(() => {
130+
const measurements = measureElement(ref)
131+
if (measurements) {
132+
setMeasurements(measurements)
133+
setWaitForInitialLoad(false)
134+
}
135+
}, 1000)
136+
137+
return () => {
138+
window.clearTimeout(initialMeasurementTimeout)
139+
}
140+
}
110141
return
111142
}
112143

@@ -127,6 +158,13 @@ export function VirtualElement({
127158
}
128159
idleCallback = window.requestIdleCallback(
129160
() => {
161+
// Measure the entire wrapper element instead of just the childRef
162+
if (ref) {
163+
const measurements = measureElement(ref)
164+
if (measurements) {
165+
setMeasurements(measurements)
166+
}
167+
}
130168
setIsShowingChildren(false)
131169
},
132170
{
@@ -146,9 +184,7 @@ export function VirtualElement({
146184
window.clearTimeout(optimizeTimeout)
147185
}
148186
}
149-
}, [inView])
150-
151-
const showPlaceholder = !isShowingChildren && !initialShow
187+
}, [ref, inView])
152188

153189
return (
154190
<InView
@@ -158,13 +194,96 @@ export function VirtualElement({
158194
className={className}
159195
as="div"
160196
>
161-
<div>
197+
<div
198+
ref={setRef}
199+
style={
200+
// We need to set undefined if the height is not known, to allow the parent to calculate the height
201+
measurements
202+
? {
203+
height: measurements.clientHeight + 'px',
204+
}
205+
: undefined
206+
}
207+
>
162208
{showPlaceholder ? (
163-
<div id={id} className={`virtual-element-placeholder ${placeholderClassName}`} style={styleObj}></div>
209+
<div
210+
id={measurements?.id ?? id}
211+
className={`virtual-element-placeholder ${placeholderClassName}`}
212+
style={styleObj}
213+
></div>
164214
) : (
165215
children
166216
)}
167217
</div>
168218
</InView>
169219
)
170220
}
221+
222+
function measureElement(wrapperEl: HTMLDivElement): IElementMeasurements | null {
223+
if (!wrapperEl || !wrapperEl.firstElementChild) {
224+
return null
225+
}
226+
227+
const el = wrapperEl.firstElementChild as HTMLElement
228+
const style = window.getComputedStyle(el)
229+
230+
// Look for the complete wrapper that contains both the timeline and dashboard
231+
const timelineWrapper = el.closest('.segment-timeline-wrapper--shelf')
232+
233+
if (timelineWrapper) {
234+
// Check if the direct child of the wrapper has an explicit height set
235+
const wrapperChild = timelineWrapper.querySelector(':scope > div')
236+
let totalHeight = 0
237+
238+
if (wrapperChild && wrapperChild instanceof HTMLElement) {
239+
// Check for explicit height style
240+
const inlineHeight = wrapperChild.style.height
241+
if (inlineHeight && inlineHeight.length > 0) {
242+
// Use the explicit height if it exists
243+
// Extract the numeric value if it's in pixels
244+
const heightValue = parseInt(inlineHeight, 10)
245+
if (!isNaN(heightValue)) {
246+
totalHeight = heightValue
247+
}
248+
}
249+
}
250+
251+
// If no explicit height was found or it wasn't parseable, fall back to measuring
252+
if (totalHeight === 0) {
253+
// Get the segment timeline height
254+
const segmentTimeline = timelineWrapper.querySelector('.segment-timeline')
255+
if (segmentTimeline) {
256+
const segmentRect = segmentTimeline.getBoundingClientRect()
257+
totalHeight = segmentRect.height
258+
}
259+
260+
// Add the dashboard panel height if it exists
261+
const dashboardPanel = timelineWrapper.querySelector('.dashboard-panel')
262+
if (dashboardPanel) {
263+
const panelRect = dashboardPanel.getBoundingClientRect()
264+
totalHeight += panelRect.height
265+
}
266+
}
267+
268+
return {
269+
width: style.width || 'auto',
270+
clientHeight: totalHeight,
271+
marginTop: style.marginTop || undefined,
272+
marginBottom: style.marginBottom || undefined,
273+
marginLeft: style.marginLeft || undefined,
274+
marginRight: style.marginRight || undefined,
275+
id: el.id,
276+
}
277+
}
278+
279+
// Fallback to just measuring the element itself if wrapper isn't found
280+
return {
281+
width: style.width || 'auto',
282+
clientHeight: el.clientHeight,
283+
marginTop: style.marginTop || undefined,
284+
marginBottom: style.marginBottom || undefined,
285+
marginLeft: style.marginLeft || undefined,
286+
marginRight: style.marginRight || undefined,
287+
id: el.id,
288+
}
289+
}

0 commit comments

Comments
 (0)