feat(grid): add autofit-all-columns action to the filter panel

This commit is contained in:
zipg
2026-09-22 14:17:18 +08:00
committed by GitHub
parent 4f0f69131b
commit 98101d20af
19 changed files with 148 additions and 5 deletions
@@ -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,
@@ -368,6 +368,12 @@ onBeforeUnmount(resetColumnDragState);
{{ t("grid.noSearchResults") }}
</div>
</div>
<div v-if="grid?.autoFitAllColumns" class="flex items-center justify-between gap-2 border-t bg-muted/30 px-2 py-1.5">
<span class="text-[11px] leading-4 text-muted-foreground">{{ t("grid.columnWidth") }}</span>
<Button variant="ghost" size="sm" class="h-7 shrink-0 px-2 text-xs" data-column-auto-fit-all :title="t('grid.autoFitColumnWidthsHint')" @click="grid?.autoFitAllColumns?.()">
{{ t("grid.autoFitColumnWidths") }}
</Button>
</div>
<div class="flex flex-col gap-1 border-t bg-muted/30 px-2 py-1.5">
<span class="text-[11px] leading-4 text-muted-foreground">
{{ t("grid.columnVisibilityHint") }}
@@ -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<HTMLButtonElement>("[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");
@@ -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 {
@@ -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"],
@@ -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,
+2
View File
@@ -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.",
+2
View File
@@ -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.",
+2
View File
@@ -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.",
+2
View File
@@ -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.",
+2
View File
@@ -2087,6 +2087,8 @@ export default withEnglishFallback({
localFiltersActive: "{count}件のローカル値フィルター",
localFilterMoreValues: " +{count}件以上",
columnVisibility: "列",
autoFitColumnWidths: "内容に合わせて調整",
autoFitColumnWidthsHint: "表示中のすべての列をセルの内容に合わせて調整します。その後も列の境界をドラッグできます。",
columnVisibilityHint: "少なくとも1列は表示されます。",
columnReorderHint: "ドラッグして列の順序を変更します。",
columnReorderSearchHint: "列の順序を変更するには検索をクリアしてください。",
+2
View File
@@ -2057,6 +2057,8 @@ export default withEnglishFallback({
localFiltersActive: "로컬 값 필터 {count}개",
localFilterMoreValues: " 외 {count}개",
columnVisibility: "컬럼",
autoFitColumnWidths: "내용에 맞게 맞춤",
autoFitColumnWidthsHint: "표시된 모든 열을 셀 내용에 맞게 조정합니다. 이후에도 열 경계를 드래그할 수 있습니다.",
columnVisibilityHint: "최소 한 개의 컬럼은 표시 상태를 유지합니다.",
columnReorderHint: "드래그하여 컬럼 순서를 변경합니다.",
columnReorderSearchHint: "컬럼 순서를 변경하려면 검색을 지우세요.",
+2
View File
@@ -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.",
+2
View File
@@ -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.",
+2
View File
@@ -2131,6 +2131,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} 个本地值筛选",
localFilterMoreValues: ",还有 {count} 个",
columnVisibility: "字段筛选",
autoFitColumnWidths: "自动适配列宽",
autoFitColumnWidthsHint: "按单元格内容调整所有可见列的宽度;之后仍可拖拽列边界手动微调。",
columnVisibilityHint: "至少保留一列可见。",
columnReorderHint: "拖动可调整字段顺序。",
columnReorderSearchHint: "清除搜索后可调整字段顺序。",
+2
View File
@@ -2076,6 +2076,8 @@ export default withEnglishFallback({
localFiltersActive: "{count} 個本機值篩選",
localFilterMoreValues: ",還有 {count} 個",
columnVisibility: "欄位篩選",
autoFitColumnWidths: "自動適配欄寬",
autoFitColumnWidthsHint: "依儲存格內容調整所有可見欄的寬度;之後仍可拖曳欄邊界手動微調。",
columnVisibilityHint: "至少保留一欄可見。",
columnReorderHint: "拖曳可調整欄位順序。",
columnReorderSearchHint: "清除搜尋後可調整欄位順序。",
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB