From 6ccfb848b3318b3083e5f76f70b93f4f8328d3ed Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sat, 13 Jun 2026 23:00:04 +0600 Subject: [PATCH 1/6] chore: update description --- .../components/data-grid-multiplayer-demo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx b/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx index 27406283..f0c5531f 100644 --- a/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx +++ b/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx @@ -299,7 +299,7 @@ export function DataGridMultiplayerDemo({ (draft as Record)[key] = skater[key]; }); - // Broadcast immediately, no Postgres round-trip needed + // Broadcast immediately, no postgres round-trip needed sendCellUpdate(existing.id, key, nv); } } From 859c55d466e149884a11ffa796af4320dbdeffb7 Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sat, 13 Jun 2026 23:01:15 +0600 Subject: [PATCH 2/6] chore: remove description --- .../components/data-grid-multiplayer-demo.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx b/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx index f0c5531f..7724a679 100644 --- a/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx +++ b/src/app/data-grid-multiplayer/components/data-grid-multiplayer-demo.tsx @@ -299,7 +299,6 @@ export function DataGridMultiplayerDemo({ (draft as Record)[key] = skater[key]; }); - // Broadcast immediately, no postgres round-trip needed sendCellUpdate(existing.id, key, nv); } } From 811d3858c6cef6fd1016e34b9e343dd153fdfeee Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sat, 13 Jun 2026 23:17:06 +0600 Subject: [PATCH 3/6] feat: add scrollpaddingstart and scrollpaddingend --- src/hooks/use-data-grid.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hooks/use-data-grid.ts b/src/hooks/use-data-grid.ts index 0dc0df91..bd27a919 100644 --- a/src/hooks/use-data-grid.ts +++ b/src/hooks/use-data-grid.ts @@ -2249,6 +2249,12 @@ function useDataGrid({ measureElement: !isFirefox ? (element) => element?.getBoundingClientRect().height : undefined, + scrollPaddingStart: + (headerRef.current?.getBoundingClientRect().height ?? 0) + + VIEWPORT_OFFSET, + scrollPaddingEnd: + (footerRef.current?.getBoundingClientRect().height ?? 0) + + VIEWPORT_OFFSET, }); if (!rowVirtualizerRef.current) { From 9eff7481e3a819a9dffd20663fb342a721b2dc89 Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sat, 13 Jun 2026 23:58:39 +0600 Subject: [PATCH 4/6] fix: scroll down sync --- src/hooks/use-data-grid.ts | 60 +++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/hooks/use-data-grid.ts b/src/hooks/use-data-grid.ts index bd27a919..649ad38e 100644 --- a/src/hooks/use-data-grid.ts +++ b/src/hooks/use-data-grid.ts @@ -2090,8 +2090,51 @@ function useDataGrid({ getIsActiveSearchMatch, getVisualRowIndex, scrollToCell: (rowIndex, columnId, align = "auto") => { + const container = dataGridRef.current; + if (!container) return; + rowVirtualizerRef.current?.scrollToIndex(rowIndex, { align }); - scrollToTargetCell(rowIndex, columnId); + + const correctVertical = (retries = 1) => { + requestAnimationFrame(() => { + const targetRow = rowMapRef.current.get(rowIndex); + if (!targetRow) { + if (retries > 0) correctVertical(retries - 1); + return; + } + + // Use the actual header bottom / footer top from the DOM so that + // borders, scrollbars and any other layout quirks are accounted for. + const headerBottom = + headerRef.current?.getBoundingClientRect().bottom ?? + container.getBoundingClientRect().top; + + const viewportTop = headerBottom + VIEWPORT_OFFSET; + + const rowRect = targetRow.getBoundingClientRect(); + // Only correct for header clipping (upward scroll). Footer clipping + // is handled by scrollPaddingEnd with a 1-row buffer so it never + // needs a post-scroll correction (which would read stale positions + // after measurement updates and over-shoot by ~1 row). + if (rowRect.top < viewportTop) { + container.scrollTop -= viewportTop - rowRect.top; + } + + const cellKey = getCellKey(rowIndex, columnId); + const targetCell = cellMapRef.current.get(cellKey); + if (targetCell) { + scrollCellIntoView({ + container, + targetCell, + tableRef, + viewportOffset: VIEWPORT_OFFSET, + isRtl: dir === "rtl", + }); + } + }); + }; + + correctVertical(); }, onRowHeightChange, onRowSelect, @@ -2122,7 +2165,7 @@ function useDataGrid({ }, [ propsRef, store, - scrollToTargetCell, + dir, getIsCellSelected, getIsSearchMatch, getIsActiveSearchMatch, @@ -2250,10 +2293,19 @@ function useDataGrid({ ? (element) => element?.getBoundingClientRect().height : undefined, scrollPaddingStart: - (headerRef.current?.getBoundingClientRect().height ?? 0) + + (headerRef.current?.getBoundingClientRect().bottom ?? 0) - + (dataGridRef.current?.getBoundingClientRect().top ?? 0) + VIEWPORT_OFFSET, + // Extra rowHeightValue buffer keeps the target row clearly above the sticky + // footer even when virtual positions shift after newly-rendered rows are + // measured. Without this, correctVertical's footer check fires on stale + // positions and over-shoots by ~1 row. scrollPaddingEnd: - (footerRef.current?.getBoundingClientRect().height ?? 0) + + (dataGridRef.current?.getBoundingClientRect().bottom ?? 0) - + (footerRef.current?.getBoundingClientRect().top ?? + dataGridRef.current?.getBoundingClientRect().bottom ?? + 0) + + rowHeightValue + VIEWPORT_OFFSET, }); From 6f3fcedcc245720b51e12ba9daa7c44f9f811b5e Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sun, 14 Jun 2026 00:06:46 +0600 Subject: [PATCH 5/6] chore: cleanup stuffs --- src/hooks/use-data-grid.ts | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/hooks/use-data-grid.ts b/src/hooks/use-data-grid.ts index 649ad38e..3cbd82a8 100644 --- a/src/hooks/use-data-grid.ts +++ b/src/hooks/use-data-grid.ts @@ -1681,7 +1681,7 @@ function useDataGrid({ }); }, [store]); - const scrollToTargetCell = React.useCallback( + const scrollToCell = React.useCallback( (rowIndex: number, columnId: string) => { requestAnimationFrame(() => { const container = dataGridRef.current; @@ -1729,14 +1729,14 @@ function useDataGrid({ isSelecting: false, }); focusCell(rowIndex, columnId); - scrollToTargetCell(rowIndex, columnId); + scrollToCell(rowIndex, columnId); return; } if (event.shiftKey && currentState.focusedCell) { event.preventDefault(); selectRange(currentState.focusedCell, { rowIndex, columnId }); - scrollToTargetCell(rowIndex, columnId); + scrollToCell(rowIndex, columnId); return; } } @@ -1754,7 +1754,7 @@ function useDataGrid({ onSelectionClear(); } else { focusCell(rowIndex, columnId); - scrollToTargetCell(rowIndex, columnId); + scrollToCell(rowIndex, columnId); return; } } else if (hasSelectedRows && columnId !== "select") { @@ -1768,13 +1768,13 @@ function useDataGrid({ onCellEditingStart(rowIndex, columnId); } else { focusCell(rowIndex, columnId); - scrollToTargetCell(rowIndex, columnId); + scrollToCell(rowIndex, columnId); } }, [ store, focusCell, - scrollToTargetCell, + scrollToCell, onCellEditingStart, selectRange, onSelectionClear, @@ -2095,16 +2095,14 @@ function useDataGrid({ rowVirtualizerRef.current?.scrollToIndex(rowIndex, { align }); - const correctVertical = (retries = 1) => { + const scrollRowIntoView = (retries = 1) => { requestAnimationFrame(() => { const targetRow = rowMapRef.current.get(rowIndex); if (!targetRow) { - if (retries > 0) correctVertical(retries - 1); + if (retries > 0) scrollRowIntoView(retries - 1); return; } - // Use the actual header bottom / footer top from the DOM so that - // borders, scrollbars and any other layout quirks are accounted for. const headerBottom = headerRef.current?.getBoundingClientRect().bottom ?? container.getBoundingClientRect().top; @@ -2112,10 +2110,7 @@ function useDataGrid({ const viewportTop = headerBottom + VIEWPORT_OFFSET; const rowRect = targetRow.getBoundingClientRect(); - // Only correct for header clipping (upward scroll). Footer clipping - // is handled by scrollPaddingEnd with a 1-row buffer so it never - // needs a post-scroll correction (which would read stale positions - // after measurement updates and over-shoot by ~1 row). + if (rowRect.top < viewportTop) { container.scrollTop -= viewportTop - rowRect.top; } @@ -2134,7 +2129,7 @@ function useDataGrid({ }); }; - correctVertical(); + scrollRowIntoView(); }, onRowHeightChange, onRowSelect, @@ -2296,10 +2291,7 @@ function useDataGrid({ (headerRef.current?.getBoundingClientRect().bottom ?? 0) - (dataGridRef.current?.getBoundingClientRect().top ?? 0) + VIEWPORT_OFFSET, - // Extra rowHeightValue buffer keeps the target row clearly above the sticky - // footer even when virtual positions shift after newly-rendered rows are - // measured. Without this, correctVertical's footer check fires on stale - // positions and over-shoots by ~1 row. + // Add extra row buffer to absorb virtual position drift from post-render measurements scrollPaddingEnd: (dataGridRef.current?.getBoundingClientRect().bottom ?? 0) - (footerRef.current?.getBoundingClientRect().top ?? From caa097adabf21d1d1670476e4950edaa042f78a3 Mon Sep 17 00:00:00 2001 From: sadmann7 Date: Sun, 14 Jun 2026 00:08:11 +0600 Subject: [PATCH 6/6] chore: update stuffs --- src/hooks/use-data-grid.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/use-data-grid.ts b/src/hooks/use-data-grid.ts index 3cbd82a8..a489bc74 100644 --- a/src/hooks/use-data-grid.ts +++ b/src/hooks/use-data-grid.ts @@ -2291,7 +2291,7 @@ function useDataGrid({ (headerRef.current?.getBoundingClientRect().bottom ?? 0) - (dataGridRef.current?.getBoundingClientRect().top ?? 0) + VIEWPORT_OFFSET, - // Add extra row buffer to absorb virtual position drift from post-render measurements + // Add extra row buffer to absorb virtual position drift after render measurements scrollPaddingEnd: (dataGridRef.current?.getBoundingClientRect().bottom ?? 0) - (footerRef.current?.getBoundingClientRect().top ??