diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue
index 034d864ea3..e26b72c467 100644
--- a/apps/desktop/src/components/grid/DataGrid.vue
+++ b/apps/desktop/src/components/grid/DataGrid.vue
@@ -2282,7 +2282,7 @@ function columnWidthDisplayValue(value: CellValue, columnIndex: number): CellVal
return showWhitespaceEnabled.value && typeof display === "string" ? gridCellDisplayValue(display, flatteningMultiLineEnabled.value, true) : display;
}
-const { initColumnWidths, onResizeStart, autoFitColumn, renderedColumnWidths, totalWidth, columnVars, getIsResizing } = useDataGridColumnResize({
+const { initColumnWidths, onResizeStart, autoFitColumn, autoFitAllColumns, renderedColumnWidths, totalWidth, columnVars, getIsResizing } = useDataGridColumnResize({
columns: visibleColumns,
sourceRows: computed(() => props.result.rows),
columnIndexes: visibleColumnIndexes,
@@ -11524,6 +11524,7 @@ defineExpose({
hasCustomColumnOrder,
moveDisplayableColumn,
resetColumnOrder,
+ autoFitAllColumns,
nullColumnsHidden,
allNullColumnCount,
canToggleAllNullColumns,
diff --git a/apps/desktop/src/components/grid/DataGridColumnLayoutPopover.vue b/apps/desktop/src/components/grid/DataGridColumnLayoutPopover.vue
index c51af7229e..7ad8b4d892 100644
--- a/apps/desktop/src/components/grid/DataGridColumnLayoutPopover.vue
+++ b/apps/desktop/src/components/grid/DataGridColumnLayoutPopover.vue
@@ -368,6 +368,12 @@ onBeforeUnmount(resetColumnDragState);
{{ t("grid.noSearchResults") }}
+
+ {{ t("grid.columnWidth") }}
+
+
{{ t("grid.columnVisibilityHint") }}
diff --git a/apps/desktop/src/components/grid/__tests__/dataGridColumnLayoutPopover.spec.ts b/apps/desktop/src/components/grid/__tests__/dataGridColumnLayoutPopover.spec.ts
index 597db006b8..00a4244de0 100644
--- a/apps/desktop/src/components/grid/__tests__/dataGridColumnLayoutPopover.spec.ts
+++ b/apps/desktop/src/components/grid/__tests__/dataGridColumnLayoutPopover.spec.ts
@@ -60,6 +60,7 @@ function createGrid(itemCount: number) {
const options = columnLayoutOptions(itemCount);
const toggleColumnVisibility = vi.fn();
const moveDisplayableColumn = vi.fn();
+ const autoFitAllColumns = vi.fn();
const grid: DataGridColumnLayoutHandle = {
visibleColumnCount: itemCount,
displayableColumnCount: itemCount,
@@ -76,10 +77,41 @@ function createGrid(itemCount: number) {
hasCustomColumnOrder: false,
moveDisplayableColumn,
resetColumnOrder: vi.fn(),
+ autoFitAllColumns,
};
- return { grid, moveDisplayableColumn, toggleColumnVisibility };
+ return { grid, moveDisplayableColumn, toggleColumnVisibility, autoFitAllColumns };
}
+// https://github.com/t8y2/dbx/issues/9813
+describe("column layout popover auto fit", () => {
+ it("offers an auto fit action that fits every visible column", async () => {
+ const { host, autoFitAllColumns } = await mountPopover(3);
+
+ expect(host.textContent).toContain("Column Width");
+ const autoFitButton = host.querySelector("[data-column-auto-fit-all]")!;
+ expect(autoFitButton).not.toBeNull();
+ expect(autoFitButton.textContent?.trim()).toBe("Fit columns to content");
+
+ autoFitButton.click();
+ expect(autoFitAllColumns).toHaveBeenCalledTimes(1);
+ });
+
+ it("hides the auto fit action for grids that cannot resize columns", async () => {
+ const gridState = createGrid(2);
+ delete (gridState.grid as { autoFitAllColumns?: unknown }).autoFitAllColumns;
+ const host = document.createElement("div");
+ document.body.append(host);
+ const app = createApp(DataGridColumnLayoutPopover, { grid: gridState.grid });
+ app.use(i18n);
+ app.mount(host);
+ mountedApps.push({ app, host });
+ await nextTick();
+ await nextTick();
+
+ expect(host.querySelector("[data-column-auto-fit-all]")).toBeNull();
+ });
+});
+
async function mountPopover(itemCount = 4) {
const gridState = createGrid(itemCount);
const host = document.createElement("div");
diff --git a/apps/desktop/src/components/grid/dataGridColumnLayoutPopover.ts b/apps/desktop/src/components/grid/dataGridColumnLayoutPopover.ts
index 021dcaefa6..ab03f04b42 100644
--- a/apps/desktop/src/components/grid/dataGridColumnLayoutPopover.ts
+++ b/apps/desktop/src/components/grid/dataGridColumnLayoutPopover.ts
@@ -19,6 +19,9 @@ export interface DataGridColumnLayoutHandle {
hasCustomColumnOrder: boolean;
moveDisplayableColumn: (fromDisplayableIndex: number, toDisplayableIndex: number) => void;
resetColumnOrder: () => void;
+ /** Fit every visible column to its content (#9813). Optional so grids that
+ * only expose the visibility/order surface keep working. */
+ autoFitAllColumns?: () => void;
}
export interface DataGridColumnLayoutVirtualWindow {
diff --git a/apps/desktop/src/composables/__tests__/useDataGridColumnResize.spec.ts b/apps/desktop/src/composables/__tests__/useDataGridColumnResize.spec.ts
index ca310f8c55..39ec10a19c 100644
--- a/apps/desktop/src/composables/__tests__/useDataGridColumnResize.spec.ts
+++ b/apps/desktop/src/composables/__tests__/useDataGridColumnResize.spec.ts
@@ -62,6 +62,56 @@ describe("useDataGridColumnResize", () => {
clearDataGridColumnWidthStates();
});
+ it("fits every visible column to its content when auto fitting the whole grid", () => {
+ const longText = "x".repeat(300);
+ const state = createResizeState({
+ columns: ["id", "note"],
+ rows: [
+ [1, longText],
+ [2, longText],
+ ],
+ });
+
+ state.initColumnWidths();
+ const before = state.renderedColumnWidths.value.slice();
+ expect(Math.max(...before)).toBeLessThan(DATA_GRID_COL_AUTO_FIT_MAX_WIDTH);
+
+ state.autoFitAllColumns();
+
+ const after = state.renderedColumnWidths.value;
+ // The long text column now uses the full auto-fit allowance instead of the
+ // density value limit that bounds the default grow-only sizing.
+ expect(after[1]).toBeGreaterThan(before[1]!);
+ expect(after[1]).toBeLessThanOrEqual(DATA_GRID_COL_AUTO_FIT_MAX_WIDTH);
+ expect(after[0]).toBeGreaterThanOrEqual(DATA_GRID_COL_MIN_WIDTH);
+ expect(after.length).toBe(before.length);
+ });
+
+ it("keeps auto fitted widths when the grid re-initialises for the same columns", () => {
+ const longText = "y".repeat(220);
+ const state = createResizeState({
+ columns: ["id", "note", "extra"],
+ rows: [[1, longText, "short"]],
+ });
+
+ state.initColumnWidths();
+ state.autoFitAllColumns();
+ const fitted = state.renderedColumnWidths.value.slice();
+
+ state.initColumnWidths();
+
+ expect(state.renderedColumnWidths.value).toEqual(fitted);
+ });
+
+ it("does not change any width when the grid has no columns yet", () => {
+ const state = createResizeState({ columns: [], rows: [] });
+
+ state.initColumnWidths();
+ state.autoFitAllColumns();
+
+ expect(state.renderedColumnWidths.value).toEqual([]);
+ });
+
it("keeps compact query result columns at content width instead of filling the viewport", () => {
const state = createResizeState({
columns: ["id", "user_id"],
diff --git a/apps/desktop/src/composables/useDataGridColumnResize.ts b/apps/desktop/src/composables/useDataGridColumnResize.ts
index 295acbc98a..d8ff7f714a 100644
--- a/apps/desktop/src/composables/useDataGridColumnResize.ts
+++ b/apps/desktop/src/composables/useDataGridColumnResize.ts
@@ -184,10 +184,13 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions)
document.addEventListener("mouseup", onUp);
}
- function autoFitColumn(colIdx: number) {
+ /** Full-content width of one visible column: unlike the default grow-only
+ * sizing this ignores the density value truncation, so long text columns are
+ * sized for what the cell actually shows. */
+ function autoFitColumnWidth(colIdx: number): number | undefined {
const colName = columns.value[colIdx];
- if (!colName) return;
- columnWidths.value[colIdx] = calculateDataGridColumnWidth({
+ if (!colName) return undefined;
+ return calculateDataGridColumnWidth({
columnName: colName,
sampleValues: sampleColumnValues(colIdx),
maxWidth: DATA_GRID_COL_AUTO_FIT_MAX_WIDTH,
@@ -198,10 +201,37 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions)
headerTextWidth: measureHeaderText?.(colName),
hasIndexIndicator: options.columnIndexIndicators?.value[colIdx] ?? false,
});
+ }
+
+ function autoFitColumn(colIdx: number) {
+ const width = autoFitColumnWidth(colIdx);
+ if (width === undefined) return;
+ columnWidths.value[colIdx] = width;
markColumnUserSized(colIdx);
persistColumnWidths();
}
+ /** Fit every visible column to its content in one action (issue #9813), so a
+ * wide table does not need one double-click per column. Manual drag keeps
+ * working afterwards: the fitted columns are recorded as user-sized, which is
+ * exactly the state a drag leaves behind. */
+ function autoFitAllColumns() {
+ if (columnWidths.value.length !== columns.value.length || columns.value.length === 0) return;
+ const next = columnWidths.value.slice();
+ let changed = false;
+ for (let colIdx = 0; colIdx < columns.value.length; colIdx++) {
+ const width = autoFitColumnWidth(colIdx);
+ if (width === undefined) continue;
+ markColumnUserSized(colIdx);
+ if (next[colIdx] !== width) {
+ next[colIdx] = width;
+ changed = true;
+ }
+ }
+ if (changed) columnWidths.value = next;
+ persistColumnWidths();
+ }
+
const renderedColumnWidths = computed(() => columnWidths.value.slice());
const resolvedRowNumberWidth = computed(() => options.rowNumberWidth?.value ?? DATA_GRID_ROW_NUM_WIDTH);
@@ -245,6 +275,7 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions)
growColumnWidthsToFitSamples,
onResizeStart,
autoFitColumn,
+ autoFitAllColumns,
renderedColumnWidths,
totalWidth,
columnVars,
diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts
index 87a84da7f4..edd96fe99e 100644
--- a/apps/desktop/src/i18n/locales/az.ts
+++ b/apps/desktop/src/i18n/locales/az.ts
@@ -2033,6 +2033,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} yerli dəyər süzgəci",
localFilterMoreValues: " +{count} əlavə",
columnVisibility: "Sütunlar",
+ autoFitColumnWidths: "Məzmuna uyğunlaşdır",
+ autoFitColumnWidthsHint: "Bütün görünən sütunları xana məzmununa uyğun ölçüləndirir. Sonra da sütun kənarını sürükləyə bilərsiniz.",
columnVisibilityHint: "Ən azı bir sütun görünən qalır.",
columnReorderHint: "Sütunların sırasını dəyişmək üçün sürükləyin.",
columnReorderSearchHint: "Sütunların sırasını dəyişmək üçün axtarışı təmizləyin.",
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 7098c70d93..2e1e40b69e 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -2218,6 +2218,8 @@ export default {
localFiltersActive: "{count} local value filters",
localFilterMoreValues: " +{count} more",
columnVisibility: "Columns",
+ autoFitColumnWidths: "Fit columns to content",
+ autoFitColumnWidthsHint: "Sizes every visible column to what its cells show. You can still drag a column border afterwards.",
columnVisibilityHint: "At least one column stays visible.",
columnReorderHint: "Drag to reorder columns.",
columnReorderSearchHint: "Clear the search to reorder columns.",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index 6486f08f98..2879c5a050 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -2079,6 +2079,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} filtros locales de valores",
localFilterMoreValues: " +{count} más",
columnVisibility: "Columnas",
+ autoFitColumnWidths: "Ajustar al contenido",
+ autoFitColumnWidthsHint: "Ajusta cada columna visible al contenido de sus celdas. Después puedes arrastrar el borde de la columna.",
columnVisibilityHint: "Al menos una columna permanece visible.",
columnReorderHint: "Arrastra para reordenar las columnas.",
columnReorderSearchHint: "Borra la búsqueda para reordenar las columnas.",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index 6ed127dc48..83b3ea4359 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -2076,6 +2076,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} filtri valori locali",
localFilterMoreValues: " +{count} altri",
columnVisibility: "Colonne",
+ autoFitColumnWidths: "Adatta al contenuto",
+ autoFitColumnWidthsHint: "Adatta ogni colonna visibile al contenuto delle celle. Puoi comunque trascinare il bordo della colonna.",
columnVisibilityHint: "Almeno una colonna deve rimanere visibile.",
columnReorderHint: "Trascina per riordinare le colonne.",
columnReorderSearchHint: "Cancella la ricerca per riordinare le colonne.",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index c10b0967c9..4765cd0d91 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -2087,6 +2087,8 @@ export default withEnglishFallback({
localFiltersActive: "{count}件のローカル値フィルター",
localFilterMoreValues: " +{count}件以上",
columnVisibility: "列",
+ autoFitColumnWidths: "内容に合わせて調整",
+ autoFitColumnWidthsHint: "表示中のすべての列をセルの内容に合わせて調整します。その後も列の境界をドラッグできます。",
columnVisibilityHint: "少なくとも1列は表示されます。",
columnReorderHint: "ドラッグして列の順序を変更します。",
columnReorderSearchHint: "列の順序を変更するには検索をクリアしてください。",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index b7fde570a5..a86770ba52 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -2057,6 +2057,8 @@ export default withEnglishFallback({
localFiltersActive: "로컬 값 필터 {count}개",
localFilterMoreValues: " 외 {count}개",
columnVisibility: "컬럼",
+ autoFitColumnWidths: "내용에 맞게 맞춤",
+ autoFitColumnWidthsHint: "표시된 모든 열을 셀 내용에 맞게 조정합니다. 이후에도 열 경계를 드래그할 수 있습니다.",
columnVisibilityHint: "최소 한 개의 컬럼은 표시 상태를 유지합니다.",
columnReorderHint: "드래그하여 컬럼 순서를 변경합니다.",
columnReorderSearchHint: "컬럼 순서를 변경하려면 검색을 지우세요.",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index bc66671457..ccac0f5350 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -2078,6 +2078,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} filtros locais de valores",
localFilterMoreValues: " +{count} mais",
columnVisibility: "Colunas",
+ autoFitColumnWidths: "Ajustar ao conteúdo",
+ autoFitColumnWidthsHint: "Ajusta cada coluna visível ao conteúdo das células. Você ainda pode arrastar a borda da coluna.",
columnVisibilityHint: "Pelo menos uma coluna permanece visível.",
columnReorderHint: "Arraste para reordenar as colunas.",
columnReorderSearchHint: "Limpe a pesquisa para reordenar as colunas.",
diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts
index 05a2069a0e..f182d21a40 100644
--- a/apps/desktop/src/i18n/locales/tr.ts
+++ b/apps/desktop/src/i18n/locales/tr.ts
@@ -2028,6 +2028,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} yerel değer filtresi",
localFilterMoreValues: " +{count} tane daha",
columnVisibility: "Sütunlar",
+ autoFitColumnWidths: "İçeriğe sığdır",
+ autoFitColumnWidthsHint: "Görünen tüm sütunları hücre içeriğine göre ayarlar. Ardından sütun kenarını yine sürükleyebilirsiniz.",
columnVisibilityHint: "En az bir sütun görünür kalır.",
columnReorderHint: "Sütunları yeniden sıralamak için sürükleyin.",
columnReorderSearchHint: "Sütunları yeniden sıralamak için aramayı temizleyin.",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 45aff239e7..f6712a7390 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -2131,6 +2131,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} 个本地值筛选",
localFilterMoreValues: ",还有 {count} 个",
columnVisibility: "字段筛选",
+ autoFitColumnWidths: "自动适配列宽",
+ autoFitColumnWidthsHint: "按单元格内容调整所有可见列的宽度;之后仍可拖拽列边界手动微调。",
columnVisibilityHint: "至少保留一列可见。",
columnReorderHint: "拖动可调整字段顺序。",
columnReorderSearchHint: "清除搜索后可调整字段顺序。",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index a37bb0876c..0cd4a83177 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -2076,6 +2076,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} 個本機值篩選",
localFilterMoreValues: ",還有 {count} 個",
columnVisibility: "欄位篩選",
+ autoFitColumnWidths: "自動適配欄寬",
+ autoFitColumnWidthsHint: "依儲存格內容調整所有可見欄的寬度;之後仍可拖曳欄邊界手動微調。",
columnVisibilityHint: "至少保留一欄可見。",
columnReorderHint: "拖曳可調整欄位順序。",
columnReorderSearchHint: "清除搜尋後可調整欄位順序。",
diff --git a/docs/screenshots/issue-9813-after.png b/docs/screenshots/issue-9813-after.png
new file mode 100644
index 0000000000..e9c54f8ebe
Binary files /dev/null and b/docs/screenshots/issue-9813-after.png differ
diff --git a/docs/screenshots/issue-9813-before.png b/docs/screenshots/issue-9813-before.png
new file mode 100644
index 0000000000..b539162331
Binary files /dev/null and b/docs/screenshots/issue-9813-before.png differ
diff --git a/docs/screenshots/issue-9813-popover.png b/docs/screenshots/issue-9813-popover.png
new file mode 100644
index 0000000000..82b3f12ff9
Binary files /dev/null and b/docs/screenshots/issue-9813-popover.png differ