diff --git a/.changeset/flat-rooms-stare.md b/.changeset/flat-rooms-stare.md new file mode 100644 index 0000000000..823aa251cc --- /dev/null +++ b/.changeset/flat-rooms-stare.md @@ -0,0 +1,6 @@ +--- +"@gradio/dataframe": patch +"gradio": patch +--- + +fix:Fix DataFrame Scroll Divergence diff --git a/js/dataframe/shared/VirtualTable.svelte b/js/dataframe/shared/VirtualTable.svelte index 9c0b0f226c..f3254789cd 100644 --- a/js/dataframe/shared/VirtualTable.svelte +++ b/js/dataframe/shared/VirtualTable.svelte @@ -40,43 +40,53 @@ ? window.requestAnimationFrame : (cb: (...args: any[]) => void) => cb(); - $: mounted && raf(() => refresh_height_map(sortedItems)); - - let content_height = 0; - async function refresh_height_map(_items: typeof items): Promise { - if (viewport_height === 0) { - return; + $: { + if (mounted && viewport_height && viewport.offsetParent) { + sortedItems, raf(refresh_height_map); } + } - // force header height calculation first - head_height = - viewport.querySelector(".thead")?.getBoundingClientRect().height || 0; - await tick(); + async function refresh_height_map(): Promise { + if (sortedItems.length < start) { + await scroll_to_index(sortedItems.length - 1, { behavior: "auto" }); + } - const { scrollTop } = viewport; + const scrollTop = Math.max(0, viewport.scrollTop); + show_scroll_button = scrollTop > 100; table_scrollbar_width = viewport.offsetWidth - viewport.clientWidth; - content_height = top - (scrollTop - head_height); - let i = start; - - while (content_height < max_height && i < _items.length) { - let row = rows[i - start]; - if (!row) { - end = i + 1; - await tick(); // render the newly visible row - row = rows[i - start]; - } - let _h = row?.getBoundingClientRect().height; - if (!_h) { - _h = average_height; + // acquire height map for currently visible rows + for (let v = 0; v < rows.length; v += 1) { + height_map[start + v] = rows[v].getBoundingClientRect().height; + } + let i = 0; + let y = head_height; + // loop items to find new start + while (i < sortedItems.length) { + const row_height = height_map[i] || average_height; + // keep a page of rows buffered above + if (y + row_height > scrollTop - max_height) { + start = i; + top = y - head_height; + break; } - const row_height = (height_map[i] = _h); + y += row_height; + i += 1; + } + + let content_height = head_height; + while (i < sortedItems.length) { + const row_height = height_map[i] || average_height; content_height += row_height; i += 1; + // keep a page of rows buffered below + if (content_height - head_height > 3 * max_height) { + break; + } } end = i; - const remaining = _items.length - end; + const remaining = sortedItems.length - end; const scrollbar_height = viewport.offsetHeight - viewport.clientHeight; if (scrollbar_height > 0) { @@ -86,20 +96,22 @@ let filtered_height_map = height_map.filter((v) => typeof v === "number"); average_height = filtered_height_map.reduce((a, b) => a + b, 0) / - filtered_height_map.length; + filtered_height_map.length || 30; bottom = remaining * average_height; - height_map.length = _items.length; - await tick(); - if (!max_height) { - actual_height = content_height + 1; - } else if (content_height < max_height) { - actual_height = content_height + 2; - } else { + if (!isFinite(bottom)) { + bottom = 200000; + } + height_map.length = sortedItems.length; + while (i < sortedItems.length) { + i += 1; + height_map[i] = average_height; + } + if (max_height && content_height > max_height) { actual_height = max_height; + } else { + actual_height = content_height; } - - await tick(); } $: scroll_and_render(selected); @@ -144,88 +156,6 @@ return true; } - function get_computed_px_amount(elem: HTMLElement, property: string): number { - if (!elem) { - return 0; - } - const compStyle = getComputedStyle(elem); - - let x = parseInt(compStyle.getPropertyValue(property)); - return x; - } - - async function handle_scroll(e: Event): Promise { - const scroll_top = viewport.scrollTop; - - show_scroll_button = scroll_top > 100; - - if (show_scroll_button) { - dispatch("scroll_top", scroll_top); - } - - rows = contents.children as HTMLCollectionOf; - const is_start_overflow = sortedItems.length < start; - - const row_top_border = get_computed_px_amount(rows[1], "border-top-width"); - - const actual_border_collapsed_width = 0; - - if (is_start_overflow) { - await scroll_to_index(sortedItems.length - 1, { behavior: "auto" }); - } - - let new_start = 0; - // acquire height map for currently visible rows - for (let v = 0; v < rows.length; v += 1) { - height_map[start + v] = rows[v].getBoundingClientRect().height; - } - let i = 0; - // start from top: thead, with its borders, plus the first border to afterwards neglect - let y = head_height + row_top_border / 2; - let row_heights = []; - // loop items to find new start - while (i < sortedItems.length) { - const row_height = height_map[i] || average_height; - row_heights[i] = row_height; - // we only want to jump if the full (incl. border) row is away - if (y + row_height + actual_border_collapsed_width > scroll_top) { - // this is the last index still inside the viewport - new_start = i; - top = y - (head_height + row_top_border / 2); - break; - } - y += row_height; - i += 1; - } - - new_start = Math.max(0, new_start); - while (i < sortedItems.length) { - const row_height = height_map[i] || average_height; - y += row_height; - i += 1; - if (y > scroll_top + viewport_height) { - break; - } - } - start = new_start; - end = i; - const remaining = sortedItems.length - end; - if (end === 0) { - end = 10; - } - average_height = (y - head_height) / end; - let remaining_height = remaining * average_height; // 0 - // compute height map for remaining items - while (i < sortedItems.length) { - i += 1; - height_map[i] = average_height; - } - bottom = remaining_height; - if (!isFinite(bottom)) { - bottom = 200000; - } - } - export async function scroll_to_index( index: number, opts: ScrollToOptions, @@ -269,7 +199,6 @@ onMount(() => { rows = contents.children as HTMLCollectionOf; mounted = true; - refresh_height_map(items); }); @@ -280,7 +209,7 @@ class:disable-scroll={disable_scroll} bind:this={viewport} bind:contentRect={viewport_box} - on:scroll={handle_scroll} + on:scroll={refresh_height_map} style="height: {height}; --bw-svt-p-top: {top}px; --bw-svt-p-bottom: {bottom}px; --bw-svt-head-height: {head_height}px; --bw-svt-foot-height: {foot_height}px; --bw-svt-avg-row-height: {average_height}px; --max-height: {max_height}px" >