Browse Source

fix(table-v2): align sizing with rendered content

dev
hechang27-sprt 3 months ago
parent
commit
739ae9fb8a
  1. 15
      packages/base/data/list-table-v2/index.ts
  2. 37
      packages/base/data/list-table-v2/list-table-v2.vue
  3. 4
      packages/base/data/list-table-v2/usePretextColumnWidths.ts
  4. 130
      packages/base/data/list-table-v2/usePretextRowHeights.ts

15
packages/base/data/list-table-v2/index.ts

@ -28,15 +28,6 @@ export { @@ -28,15 +28,6 @@ export {
// Hooks
export { usePretextColumnWidths, computeFlexWidths, type ColumnFlexConfig } from "./usePretextColumnWidths";
export { usePretextRowHeights, type RowHeightEntry } from "./usePretextRowHeights";
export {
useVirtualRows,
buildOffsets,
type VirtualRow,
type VirtualRange,
} from "./useVirtualRows";
export {
useRuntimeHeightAugment,
type HeightSample,
type ColumnHeightStats,
} from "./useRuntimeHeightAugment";
export { resolveRowHeights, type RowHeightEntry } from "./usePretextRowHeights";
export { useVirtualRows, buildOffsets, type VirtualRow, type VirtualRange } from "./useVirtualRows";
export { useRuntimeHeightAugment, type HeightSample, type ColumnHeightStats } from "./useRuntimeHeightAugment";

37
packages/base/data/list-table-v2/list-table-v2.vue

@ -8,8 +8,10 @@ @@ -8,8 +8,10 @@
<div>
Cell Heights:
{{
rowHeights.map((entry) =>
entry.cellHeights?.map((entry) => `${entry.isCustomRenderer ? "*" : ""}${entry.height}`).join(",")
cellHeights?.map((rowCellHeights) =>
rowCellHeights
.map((entry) => (entry ? `${entry.isCustomRenderer ? "*" : ""}${entry.height}` : "null"))
.join(",")
)
}}
</div>
@ -92,7 +94,7 @@ import { useI18n } from "vue3-i18n"; @@ -92,7 +94,7 @@ import { useI18n } from "vue3-i18n";
import * as lodash from "lodash-es";
import type { ListTableColumn, ListTableProps, PageResponse } from "./types";
import { usePretextColumnWidths, type ColumnFlexConfig } from "./usePretextColumnWidths";
import { usePretextRowHeights, type RowHeightEntry } from "./usePretextRowHeights";
import { resolveRowHeights, type RowHeightEntry } from "./usePretextRowHeights";
import { useVirtualRows } from "./useVirtualRows";
const DEFAULT_FONT = "14px sans-serif";
@ -260,22 +262,20 @@ const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths @@ -260,22 +262,20 @@ const { computedConfigs, totalFlexBasis, columnWidths } = usePretextColumnWidths
{
font: "14px Inter, sans-serif",
headerFont: "bold 14px Inter, sans-serif",
formatCellValue,
}
);
// Row heights via pretext - use actual column widths from flexbox algorithm
const { rowHeights, totalHeight } = usePretextRowHeights(pageDataRef, columnsRef, columnWidths, formatCellValue, {
const { rowHeights, cellHeights } = resolveRowHeights(pageDataRef, columnsRef, columnWidths, formatCellValue, {
font: props.font,
lineHeight: 20,
debug: props.debug,
fixedRowHeight: props.rowHeight,
});
// Virtualizer (needs rowHeights as number[] and viewportHeight)
const virtualizer = useVirtualRows(
computed(() => rowHeights.value.map((r) => r.height)),
viewportHeight,
{ overscan: 5 }
);
const virtualizer = useVirtualRows(rowHeights, viewportHeight, { overscan: 5 });
// Destructure for template auto-unwrap (Vue 3 auto-unwraps top-level refs)
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;
@ -313,7 +313,6 @@ const tableContainerStyle = computed(() => { @@ -313,7 +313,6 @@ const tableContainerStyle = computed(() => {
// =============================================================================
function getColumnStyle(col: ListTableColumn<T>): Record<string, string> {
const config = computedConfigs.value.find((c) => c.key === col.key);
const explicitWidth = col.width !== undefined && col.width !== "auto" ? Number(col.width) : undefined;
if (explicitWidth !== undefined && Number.isFinite(explicitWidth) && explicitWidth > 0) {
return {
@ -327,16 +326,25 @@ function getColumnStyle(col: ListTableColumn<T>): Record<string, string> { @@ -327,16 +326,25 @@ function getColumnStyle(col: ListTableColumn<T>): Record<string, string> {
// Find the column index to get the computed width
const colIndex = columnsRef.value.findIndex((c) => c.key === col.key);
const computedWidth = colIndex >= 0 ? columnWidths.value[colIndex] : 0;
const fallbackBasis = Number(col.minWidth ?? 120);
if (!config) {
return { flex: "1 1 100px", minWidth: "50px", maxWidth: "300px" };
return {
flexGrow: "1.1",
flexShrink: "1.1",
flexBasis: `${fallbackBasis}px`,
minWidth: `${Math.max(fallbackBasis, 50)}px`,
maxWidth: "300px",
};
}
// Apply the computed flex factors to the real cells so the row can consume
// any remaining space after fixed-width columns and scrollbar width.
const flexBasis = computedWidth > 0 ? computedWidth : config.flexBasis;
return {
flex: `${config.flexGrow} ${config.flexShrink} ${flexBasis}px`,
flexGrow: `${config.flexGrow}`,
flexShrink: `${config.flexShrink}`,
flexBasis: `${flexBasis}px`,
minWidth: `${config.minWidth}px`,
maxWidth: `${config.maxWidth}px`,
};
@ -455,6 +463,7 @@ onUnmounted(() => { @@ -455,6 +463,7 @@ onUnmounted(() => {
.header-cell {
display: flex;
flex: 1 1 120px;
align-items: center;
justify-content: center;
padding: 8px 4px;
@ -462,6 +471,7 @@ onUnmounted(() => { @@ -462,6 +471,7 @@ onUnmounted(() => {
border-right: 1px solid v-bind("state.style.tableBorderColor");
overflow: hidden;
word-break: break-word;
min-width: 0;
&:last-child {
border-right: none;
@ -510,6 +520,7 @@ onUnmounted(() => { @@ -510,6 +520,7 @@ onUnmounted(() => {
.table-cell {
display: flex;
flex: 1 1 120px;
align-items: center;
padding: 8px 4px;
box-sizing: border-box;
@ -517,6 +528,7 @@ onUnmounted(() => { @@ -517,6 +528,7 @@ onUnmounted(() => {
overflow: hidden;
word-break: break-word;
min-height: 100%;
min-width: 0;
&:last-child {
border-right: none;
@ -530,6 +542,7 @@ onUnmounted(() => { @@ -530,6 +542,7 @@ onUnmounted(() => {
text-overflow: clip;
white-space: normal;
word-break: break-word;
text-align: center;
color: v-bind("state.style.tableColor");
font: v-bind("props.font");
}

4
packages/base/data/list-table-v2/usePretextColumnWidths.ts

@ -192,11 +192,13 @@ export function usePretextColumnWidths<T>( @@ -192,11 +192,13 @@ export function usePretextColumnWidths<T>(
font?: string;
headerFont?: string;
sampleSize?: number;
formatCellValue?: (row: T, col: ListTableColumn<T>) => string;
}
) {
const font = options?.font ?? DEFAULT_FONT;
const headerFont = options?.headerFont ?? DEFAULT_HEADER_FONT;
const sampleSize = options?.sampleSize ?? 100;
const formatCellValue = options?.formatCellValue;
const computedConfigs = computed<ColumnFlexConfig[]>(() => {
if (!columns.value.length) return [];
@ -222,7 +224,7 @@ export function usePretextColumnWidths<T>( @@ -222,7 +224,7 @@ export function usePretextColumnWidths<T>(
const cellWidths: number[] = [];
for (const row of sampled) {
const rawValue = (row as any)[col.dataKey || colKey];
const cellText = rawValue == null ? "" : String(rawValue);
const cellText = formatCellValue?.(row, col) ?? (rawValue == null ? "" : String(rawValue));
if (cellText) {
const w = measureShrinkWrapWidth(cellText, font) + CELL_PADDING;
cellWidths.push(w);

130
packages/base/data/list-table-v2/usePretextRowHeights.ts

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
import { computed, type Ref } from "vue";
import { measureTextHeight } from "./measureText";
import type { ListTableColumn } from "./types";
import { toRefs } from "@vueuse/core";
const DEFAULT_FONT = "14px Inter, sans-serif";
const DEFAULT_LINE_HEIGHT = 20;
@ -17,10 +18,9 @@ const CELL_VERTICAL_PADDING = 12; // top + bottom per cell @@ -17,10 +18,9 @@ const CELL_VERTICAL_PADDING = 12; // top + bottom per cell
export interface RowHeightEntry {
height: number;
isCustomRenderer: boolean;
cellHeights?: RowHeightEntry[];
}
export function usePretextRowHeights<T>(
export function resolveRowHeights<T>(
data: Ref<T[]>,
columns: Ref<ListTableColumn<T>[]>,
columnWidths: Ref<number[]>,
@ -29,6 +29,7 @@ export function usePretextRowHeights<T>( @@ -29,6 +29,7 @@ export function usePretextRowHeights<T>(
font?: string;
lineHeight?: number;
rowPadding?: number;
fixedRowHeight?: number;
debug?: boolean;
}
) {
@ -36,74 +37,85 @@ export function usePretextRowHeights<T>( @@ -36,74 +37,85 @@ export function usePretextRowHeights<T>(
const lineHeight = options?.lineHeight ?? DEFAULT_LINE_HEIGHT;
const rowPadding = options?.rowPadding ?? DEFAULT_ROW_PADDING;
const rowHeights = computed<RowHeightEntry[]>(() => {
if (!data.value.length || !columns.value.length || !columnWidths.value.length) {
return [];
}
return data.value.map((row) => {
let maxCellHeight = lineHeight; // minimum 1 line
let cellHeights: RowHeightEntry[] | undefined = options?.debug ? [] : undefined;
for (let i = 0; i < columns.value.length; i++) {
const col = columns.value[i];
// Use flex basis width for height calculation as an approximation
// of actual column width (CSS flex layout determines actual width)
const colWidth = columnWidths.value[i] ?? 100;
// Check if custom renderer exists (we can't measure these with pretext)
const hasCustomRenderer = !!(col.cellRenderer || col.slot);
if (hasCustomRenderer) {
// For custom renderers, we use a placeholder height
// Actual height will be measured at runtime via useRuntimeHeightAugment
// For now, use a reasonable minimum
const placeholderHeight = 44; // default row height
maxCellHeight = Math.max(maxCellHeight, placeholderHeight);
cellHeights?.push({
height: placeholderHeight,
isCustomRenderer: true,
});
continue;
}
// Get raw cell value
const cellText = formatCellValue(row, col);
if (!cellText) continue;
// Calculate available width for text (excluding cell padding)
const availableWidth = colWidth - CELL_VERTICAL_PADDING * 2;
if (availableWidth <= 0) continue;
try {
const cellHeight = measureTextHeight(cellText, font, availableWidth, lineHeight);
maxCellHeight = Math.max(maxCellHeight, cellHeight);
const { rowHeights, cellHeights } = toRefs(
computed(() => {
if (!data.value.length || !columns.value.length || !columnWidths.value.length) {
return { rowHeights: [], cellHeights: [] };
}
cellHeights?.push({
height: cellHeight,
isCustomRenderer: false,
});
} catch {
// Fallback: assume single line
const rowHeights: number[] = [];
const cellHeights: Array<RowHeightEntry | null>[] | undefined = options?.debug ? [] : undefined;
for (const row of data.value) {
let maxCellHeight = lineHeight; // minimum 1 line
let rowCellHeights: Array<RowHeightEntry | null> = [];
for (let i = 0; i < columns.value.length; i++) {
const col = columns.value[i];
// Use flex basis width for height calculation as an approximation
// of actual column width (CSS flex layout determines actual width)
const colWidth = columnWidths.value[i] ?? 100;
// Check if custom renderer exists (we can't measure these with pretext)
const isCustomRenderer = !!(col.cellRenderer || col.slot);
let rowHeightEntry: RowHeightEntry;
if (options?.fixedRowHeight) {
rowHeightEntry = {
height: options.fixedRowHeight,
isCustomRenderer,
};
} else if (isCustomRenderer) {
rowHeightEntry = {
height: 42,
isCustomRenderer,
};
} else {
try {
const cellText = formatCellValue(row, col);
if (!cellText) throw "Invalid Cell Text";
// Calculate available width for text (excluding cell padding)
const availableWidth = colWidth - CELL_VERTICAL_PADDING * 2;
if (availableWidth <= 0) throw "Invalid Column Width";
rowHeightEntry = {
height: measureTextHeight(cellText, font, availableWidth, lineHeight),
isCustomRenderer,
};
} catch {
// Fallback: assume single line
rowHeightEntry = {
height: 42,
isCustomRenderer,
};
}
}
maxCellHeight = Math.max(maxCellHeight, rowHeightEntry?.height ?? 0);
if (options?.debug) {
rowCellHeights.push(rowHeightEntry);
}
}
}
const totalHeight = maxCellHeight + rowPadding * 2;
const rowHeight = maxCellHeight + rowPadding * 2;
rowHeights.push(rowHeight);
cellHeights?.push(rowCellHeights);
}
return {
height: totalHeight,
isCustomRenderer: false,
cellHeights: cellHeights ?? undefined,
rowHeights,
cellHeights,
};
});
});
})
);
// Total height (sum of all row heights) - useful for virtualizer
const totalHeight = computed(() => rowHeights.value.reduce((sum, entry) => sum + entry.height, 0));
const totalHeight = computed(() => rowHeights.value.reduce((sum, height) => sum + height, 0));
return {
rowHeights,
totalHeight,
cellHeights,
};
}

Loading…
Cancel
Save