From 2b7264a53102432145c76617f53255599ca990d2 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 11 Jun 2026 11:08:51 +0800 Subject: [PATCH] feat: data grid search also matches column names --- apps/desktop/src/components/grid/DataGrid.vue | 34 +++++++++++++++---- .../desktop/src/lib/canvasDataGridRenderer.ts | 3 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index d1130cb820..2d0b980386 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -2438,20 +2438,31 @@ watch( ); interface SearchMatch { - displayRow: number; + kind: "cell" | "column"; + displayRow: number; // -1 for column matches col: number; } const searchMatches = computed(() => { const q = deferredClientSearchText.value; if (!q) return []; + const lowered = q.toLowerCase(); const items = displayItems.value; const matches: SearchMatch[] = []; + + // Column name matches (navigate first) + for (let c = 0; c < props.result.columns.length; c++) { + if (props.result.columns[c].toLowerCase().includes(lowered)) { + matches.push({ kind: "column", displayRow: -1, col: c }); + } + } + + // Cell value matches for (let r = 0; r < items.length; r++) { const data = items[r].data; for (let c = 0; c < data.length; c++) { - if (data[c] !== null && formatCellCached(data[c], c).toLowerCase().includes(q)) { - matches.push({ displayRow: r, col: c }); + if (data[c] !== null && formatCellCached(data[c], c).toLowerCase().includes(lowered)) { + matches.push({ kind: "cell", displayRow: r, col: c }); } } } @@ -2461,7 +2472,7 @@ const searchMatches = computed(() => { const searchMatchSet = computed(() => { const set = new Set(); for (const m of searchMatches.value) { - set.add(`${m.displayRow}:${m.col}`); + set.add(`${m.kind}:${m.displayRow}:${m.col}`); } return set; }); @@ -2479,14 +2490,14 @@ watch(searchMatches, (matches) => { function cellIsSearchMatch(displayRow: number, col: number): boolean { if (isScrolling.value) return false; - return searchMatchSet.value.has(`${displayRow}:${col}`); + return searchMatchSet.value.has(`cell:${displayRow}:${col}`); } function cellIsCurrentMatch(displayRow: number, col: number): boolean { if (isScrolling.value) return false; const m = currentSearchMatch.value; if (!m) return false; - return m.displayRow === displayRow && m.col === col; + return m.kind === "cell" && m.displayRow === displayRow && m.col === col; } function navigateMatch(delta: number) { @@ -2502,6 +2513,16 @@ function scrollToCurrentMatch() { const match = searchMatches.value[idx]; const visibleColIdx = visibleColumnIndexes.value.indexOf(match.col); if (visibleColIdx >= 0) scrollGridColumnIntoView(visibleColIdx); + if (match.kind === "column") { + // Scroll to top so the column header is visible + const scrollEl = gridRef.value; + if (scrollEl) scrollEl.scrollTop = 0; + if (useCanvasGridRows.value) { + const scroller = canvasScrollerElement(); + if (scroller) scroller.scrollTop = 0; + } + return; + } const scrollEl = gridRef.value; if (!scrollEl) return; if (useCanvasGridRows.value) { @@ -6347,6 +6368,7 @@ const gridContextMenuItems = computed(() => { class="shrink-0 px-2 py-1.5 border-r border-border whitespace-nowrap hover:bg-accent/60 select-none relative overflow-hidden" :class="{ 'bg-primary/15 ring-1 ring-inset ring-primary/40': highlightedColumnIndex === col.actualColIdx || columnIsSelected(col.visibleColIdx), + 'bg-amber-500/20 ring-1 ring-inset ring-amber-500/40': currentSearchMatch?.kind === 'column' && currentSearchMatch.col === col.actualColIdx, }" :style="renderedColumnStyle(col.visibleColIdx)" :data-grid-column-index="col.actualColIdx" diff --git a/apps/desktop/src/lib/canvasDataGridRenderer.ts b/apps/desktop/src/lib/canvasDataGridRenderer.ts index 9cff8fdb9c..a71ce0a8c4 100644 --- a/apps/desktop/src/lib/canvasDataGridRenderer.ts +++ b/apps/desktop/src/lib/canvasDataGridRenderer.ts @@ -30,6 +30,7 @@ export interface CanvasSingleSelectedCell { } export interface CanvasSearchMatch { + kind: "cell" | "column"; displayRow: number; col: number; } @@ -324,7 +325,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) { const isDirtyCell = item.isDirtyCol[actualColIdx]; const selectedFillVisual = rowSelectionVisual || (selectedCell && (!isSingleSelectedCell || isDirtyCell) && (!rowIsActive || isDirtyCell)); const selectedBorderVisual = rowSelectionVisual || selectedCell; - const isSearchMatch = paintSearchMatches && searchMatchKeys.has(`${item.displayIndex}:${actualColIdx}`); + const isSearchMatch = paintSearchMatches && searchMatchKeys.has(`cell:${item.displayIndex}:${actualColIdx}`); const isCurrentSearchMatch = paintSearchMatches && currentSearchMatch?.displayRow === item.displayIndex && currentSearchMatch.col === actualColIdx; const clippedX = Math.max(x, rowNumberWidth); const cellPaintWidth = colWidth - Math.max(0, clippedX - x);