mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-02 02:34:42 +08:00
feat(grid): add autofit-all-columns action to the filter panel
This commit is contained in:
@@ -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") }}
|
||||
|
||||
+33
-1
@@ -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,
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -2087,6 +2087,8 @@ export default withEnglishFallback({
|
||||
localFiltersActive: "{count}件のローカル値フィルター",
|
||||
localFilterMoreValues: " +{count}件以上",
|
||||
columnVisibility: "列",
|
||||
autoFitColumnWidths: "内容に合わせて調整",
|
||||
autoFitColumnWidthsHint: "表示中のすべての列をセルの内容に合わせて調整します。その後も列の境界をドラッグできます。",
|
||||
columnVisibilityHint: "少なくとも1列は表示されます。",
|
||||
columnReorderHint: "ドラッグして列の順序を変更します。",
|
||||
columnReorderSearchHint: "列の順序を変更するには検索をクリアしてください。",
|
||||
|
||||
@@ -2057,6 +2057,8 @@ export default withEnglishFallback({
|
||||
localFiltersActive: "로컬 값 필터 {count}개",
|
||||
localFilterMoreValues: " 외 {count}개",
|
||||
columnVisibility: "컬럼",
|
||||
autoFitColumnWidths: "내용에 맞게 맞춤",
|
||||
autoFitColumnWidthsHint: "표시된 모든 열을 셀 내용에 맞게 조정합니다. 이후에도 열 경계를 드래그할 수 있습니다.",
|
||||
columnVisibilityHint: "최소 한 개의 컬럼은 표시 상태를 유지합니다.",
|
||||
columnReorderHint: "드래그하여 컬럼 순서를 변경합니다.",
|
||||
columnReorderSearchHint: "컬럼 순서를 변경하려면 검색을 지우세요.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -2131,6 +2131,8 @@ export default withEnglishFallback({
|
||||
localFiltersActive: "{count} 个本地值筛选",
|
||||
localFilterMoreValues: ",还有 {count} 个",
|
||||
columnVisibility: "字段筛选",
|
||||
autoFitColumnWidths: "自动适配列宽",
|
||||
autoFitColumnWidthsHint: "按单元格内容调整所有可见列的宽度;之后仍可拖拽列边界手动微调。",
|
||||
columnVisibilityHint: "至少保留一列可见。",
|
||||
columnReorderHint: "拖动可调整字段顺序。",
|
||||
columnReorderSearchHint: "清除搜索后可调整字段顺序。",
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user