diff --git a/webview-ui/src/components/DataGrid.svelte b/webview-ui/src/components/DataGrid.svelte index f5e9d50..b851c16 100644 --- a/webview-ui/src/components/DataGrid.svelte +++ b/webview-ui/src/components/DataGrid.svelte @@ -259,6 +259,19 @@ const selectedRange = $derived(gridStore.selectedRange); const frozenCols = $derived(gridStore.frozenCols); + // Visible position of the column that stretches to absorb any leftover + // horizontal space, so the grid spans the full pane with no blank + // "phantom column" on the right. We grow the last visible column unless it's + // frozen (sticky positioning + flex-grow don't mix cleanly). When the columns + // already overflow the pane, flex-grow has nothing to add and the column keeps + // its natural width (horizontal scroll kicks in as before). + const fillColPos = $derived.by(() => { + const cols = gridStore.visibleSchema; + const last = cols.length - 1; + if (last < 0) return -1; + return frozenCols.has(cols[last].index) ? -1 : last; + }); + // Returns the sticky `left` offset (in px) for a frozen column at visual position `colPos`. // Row-number column is 56px + 1px border = 57px. Sums widths of all frozen cols before this one. function getFrozenLeft(col: import('@shared/schema.js').ColumnSchema, colPos: number): number { @@ -465,6 +478,7 @@ class="header-cell" class:header-cell-selected={colSel} class:col-frozen={isFrozen} + class:col-fill={colPos === fillColPos} class:drag-over={dragOverPos === colPos && dragFromPos !== colPos} role="columnheader" tabindex="-1" @@ -519,6 +533,7 @@ onCellClick(e, row, col.index)} ondblclick={() => startEdit(row, col.index)} @@ -827,6 +843,17 @@ box-sizing: border-box; } + /* The last visible column stretches to absorb leftover horizontal space so the + grid fills the pane with no blank phantom column on the right. The inline + `width` acts as the flex-basis and flex-shrink stays 0, so the column only + ever grows past its natural width — when columns overflow the pane there's + nothing to absorb and it keeps its set width (horizontal scroll as before). */ + .header-cell.col-fill, + .cell.col-fill, + .cell-input.col-fill { + flex-grow: 1; + } + /* ── Coloured mode ────────────────────────────────────────────────────── When the user enables column colours, two things change: 1. Column separators get a slightly stronger border so the pastels diff --git a/webview-ui/src/stores/grid.svelte.ts b/webview-ui/src/stores/grid.svelte.ts index 1c6dc88..0b0848a 100644 --- a/webview-ui/src/stores/grid.svelte.ts +++ b/webview-ui/src/stores/grid.svelte.ts @@ -102,6 +102,11 @@ class GridStore { // Auto-fitted column widths, keyed by column name. Computed once after data load. private _autoWidths = new Map(); + // Guards the one-shot auto-width pass for worker-backed formats (JSON/Parquet/ + // Arrow/Excel), which never populate _csvAllRows and so miss the CSV/raw-rows + // auto-width path. Reset on every worker READY so a new dataset (or Excel sheet + // switch) re-fits its columns. + private _autoWidthsDone = false; // Viewport visibleStartRow = $state(0); @@ -1523,6 +1528,7 @@ class GridStore { this.schema = msg.payload.schema; this.totalRows = msg.payload.totalRows; this.filteredRows = msg.payload.totalRows; + this._autoWidthsDone = false; if (msg.payload.availableSheets) { this.availableSheets = msg.payload.availableSheets; this.selectedSheet = msg.payload.selectedSheet ?? msg.payload.availableSheets[0] ?? ''; @@ -1534,6 +1540,14 @@ class GridStore { case 'CHUNK': { const { requestId, rows, startRow, filteredTotal } = msg.payload; + // Worker-backed formats (JSON/Parquet/Arrow/Excel) never populate + // _csvAllRows, so the CSV/raw-rows auto-width path never runs for them. + // Fit widths once from the first chunk (natural order, no filter/sort on + // initial load) so columns size to their content like CSV does. + if (startRow === 0 && !this._autoWidthsDone && rows.length > 0 && this.schema.length > 0) { + this._computeAutoWidths(this.schema, rows); + this._autoWidthsDone = true; + } this._storeChunk(startRow, rows, requestId); this.filteredRows = filteredTotal; const resolve = this._pendingRequests.get(requestId);