perf(layout): track panel resizing at a stepped cadence

This commit is contained in:
dengjie zhang authored and GitHub committed 2026-09-18 01:48:27 +08:00
1 parent a78fc48a7b
commit 8eba12ea2f
17 files changed
+705 -34

No files matched your search

+83 -28
View File
@@ -296,6 +296,8 @@ import { useDataGridColumnLayout, useDataGridColumnLayoutState, type ColumnHeade
import { createColumnReferencePayload, createTableReferenceDragEndEvent, createTableReferenceDropEvent, createTableReferenceHoverEvent } from "@/lib/editor/queryEditorTableDrop";
import { beginTableReferenceDragFeedback, isOverSqlEditorTarget, type TableReferenceDragFeedback } from "@/lib/editor/tableReferenceDragFeedback";
import { dataGridCanvasDevicePixelSize, useDataGridCanvasRuntime, type DataGridCanvasRuntime } from "@/composables/useDataGridCanvasRuntime";
import { deferUntilPanelResizeEnd, isPanelResizing } from "@/lib/app/panelResizeState";
import { uiTuning } from "@/lib/app/uiTuning";
import { useDataGridScrollbars, type DataGridScrollbarsRuntime } from "@/composables/useDataGridScrollbars";
import { useDataGridSelection } from "@/composables/useDataGridSelection";
import { dataGridNavigationOrigin, dataGridPageScrollTop, dataGridRowScrollTop, moveDataGridCell, navigateDataGridCell, type DataGridNavigationDirection, type DataGridScrollAlignment } from "@/lib/dataGrid/dataGridNavigation";
@@ -2591,43 +2593,56 @@ function observeGridHorizontalScrollbarScroller() {
gridScrollbarsRuntime.observeScroller();
}
let dataGridTopbarRafId: number | null = null;
function updateDataGridTopbarWidth() {
const topbar = dataGridTopbarRef.value;
const previousActionCount = compactDataGridToolbarActionCount.value;
dataGridTopbarWidth.value = topbar?.clientWidth ?? 0;
dataGridViewportWidth.value = typeof window === "undefined" ? 0 : window.innerWidth;
if (!topbar) return;
if (compactDataGridToolbarActionCount.value !== previousActionCount) {
scheduleDataGridTopbarRecheck();
return;
}
// Reading scrollWidth/clientWidth here forces a document-wide synchronous
// relayout; while a divider drag is in flight that stacks on top of
// CodeMirror's measurement. Defer to a single re-measure when the drag ends.
if (deferUntilPanelResizeEnd(updateDataGridTopbarWidth)) return;
if (dataGridTopbarOverflowCompact.value || dataGridTopbarOverflowActionCount.value > 0) {
if (dataGridTopbarWidth.value >= dataGridTopbarExpandedRequiredWidth.value) {
dataGridTopbarOverflowCompact.value = false;
dataGridTopbarOverflowActionCount.value = 0;
dataGridTopbarExpandedRequiredWidth.value = 0;
const performMeasure = () => {
dataGridTopbarRafId = null;
const currentTopbar = dataGridTopbarRef.value;
if (!currentTopbar) return;
const previousActionCount = compactDataGridToolbarActionCount.value;
dataGridTopbarWidth.value = currentTopbar.clientWidth ?? 0;
dataGridViewportWidth.value = typeof window === "undefined" ? 0 : window.innerWidth;
if (compactDataGridToolbarActionCount.value !== previousActionCount) {
scheduleDataGridTopbarRecheck();
return;
}
}
// The action list gains preview/save/rollback controls in editable grids. The
// responsive breakpoint cannot see that extra width, so progressively
// compact the leftmost controls before overflow-clip hides a trailing action.
if (topbar.scrollWidth > topbar.clientWidth + 1) {
dataGridTopbarExpandedRequiredWidth.value = Math.max(dataGridTopbarExpandedRequiredWidth.value, topbar.scrollWidth + DATA_GRID_TOPBAR_EXPAND_HYSTERESIS_PX);
if (!compactDataGridToolbar.value) {
dataGridTopbarOverflowCompact.value = true;
} else if (compactDataGridToolbarActionCount.value < DATA_GRID_TOOLBAR_ACTION_COLLAPSE_ORDER.length) {
dataGridTopbarOverflowActionCount.value = compactDataGridToolbarActionCount.value + 1;
} else {
return;
if (dataGridTopbarOverflowCompact.value || dataGridTopbarOverflowActionCount.value > 0) {
if (dataGridTopbarWidth.value >= dataGridTopbarExpandedRequiredWidth.value) {
dataGridTopbarOverflowCompact.value = false;
dataGridTopbarOverflowActionCount.value = 0;
dataGridTopbarExpandedRequiredWidth.value = 0;
scheduleDataGridTopbarRecheck();
return;
}
}
scheduleDataGridTopbarRecheck();
}
if (currentTopbar.scrollWidth > currentTopbar.clientWidth + 1) {
dataGridTopbarExpandedRequiredWidth.value = Math.max(dataGridTopbarExpandedRequiredWidth.value, currentTopbar.scrollWidth + DATA_GRID_TOPBAR_EXPAND_HYSTERESIS_PX);
if (!compactDataGridToolbar.value) {
dataGridTopbarOverflowCompact.value = true;
} else if (compactDataGridToolbarActionCount.value < DATA_GRID_TOOLBAR_ACTION_COLLAPSE_ORDER.length) {
dataGridTopbarOverflowActionCount.value = compactDataGridToolbarActionCount.value + 1;
} else {
return;
}
scheduleDataGridTopbarRecheck();
}
};
if (dataGridTopbarRafId !== null) cancelAnimationFrame(dataGridTopbarRafId);
dataGridTopbarRafId = requestAnimationFrame(performMeasure);
}
function resetDataGridTopbarOverflowCompact() {
@@ -6576,7 +6591,7 @@ function dataGridRowFromClientPoint(_clientX: number, clientY: number): number |
return Number.isInteger(rowIndex) ? rowIndex : null;
}
function syncCanvasViewport(entries?: readonly ResizeObserverEntry[]) {
function syncCanvasViewportRaw(entries?: readonly ResizeObserverEntry[]) {
if (!dataGridIsActive) return;
const scroller = canvasScrollerElement();
if (!scroller) return;
@@ -6591,6 +6606,45 @@ function syncCanvasViewport(entries?: readonly ResizeObserverEntry[]) {
canvasRuntime?.drawNow();
}
// Public entry: ResizeObserver / imperative callers. During divider drags the
// step tracker owns refreshes, so plain notifications are dropped here.
function syncCanvasViewport(entries?: readonly ResizeObserverEntry[]) {
if (!dataGridIsActive) return;
if (isPanelResizing.value) return;
syncCanvasViewportRaw(entries);
}
// During divider drags the ResizeObserver would fire every frame; re-measure
// and redraw the canvas on a fixed step instead (same cadence as the SQL
// editor's pinned-box tracking, tunable via ~/.dbx/ui-tuning.json) so the
// grid follows the drag at reduced cost.
let canvasTrackFrameId = 0;
let canvasTrackFrameCount = 0;
function canvasTrackStep() {
canvasTrackFrameId = requestAnimationFrame(canvasTrackStep);
if (++canvasTrackFrameCount < uiTuning.value.panelResizeTrackEveryFrames) return;
canvasTrackFrameCount = 0;
syncCanvasViewportRaw();
}
function stopCanvasResizeTracking() {
if (canvasTrackFrameId) {
cancelAnimationFrame(canvasTrackFrameId);
canvasTrackFrameId = 0;
}
canvasTrackFrameCount = 0;
}
watch(isPanelResizing, (resizing) => {
if (resizing) {
if (!canvasTrackFrameId) canvasTrackFrameId = requestAnimationFrame(canvasTrackStep);
} else {
stopCanvasResizeTracking();
syncCanvasViewportRaw();
}
});
function currentCanvasDevicePixelRatio(): number {
return typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
}
@@ -7266,6 +7320,7 @@ function pauseCanvasGridWork() {
gridRef.value?.setAttribute("data-grid-active", "false");
if (gridSurfaceBusy.value) finishDataGridNativeSelectionBlock(dataGridNativeSelectionBlockOwner);
canvasRuntime.pause();
stopCanvasResizeTracking();
gridScrollbarsRuntime.pause();
disconnectCellEditResizeObserver();
dataGridTopbarResizeObserver?.disconnect();