forked from mengyxu/noob-components
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
3.6 KiB
150 lines
3.6 KiB
|
3 months ago
|
/**
|
||
|
|
* useRuntimeHeightAugment
|
||
|
|
*
|
||
|
|
* For columns with custom cellRenderer that return {vnode, minHeight, minWidth},
|
||
|
|
* we need to measure actual DOM height after render and maintain a running
|
||
|
|
* average per column to self-adjust row heights.
|
||
|
|
*/
|
||
|
|
import { ref, reactive, type Ref } from "vue";
|
||
|
|
|
||
|
|
const SAMPLE_THRESHOLD = 5; // Minimum samples before updating average
|
||
|
|
const RECOMPUTE_THRESHOLD = 0.1; // 10% shift triggers recompute
|
||
|
|
|
||
|
|
export interface HeightSample {
|
||
|
|
columnKey: string;
|
||
|
|
height: number;
|
||
|
|
timestamp: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ColumnHeightStats {
|
||
|
|
columnKey: string;
|
||
|
|
samples: number[];
|
||
|
|
average: number;
|
||
|
|
count: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useRuntimeHeightAugment() {
|
||
|
|
// Map from columnKey -> stats
|
||
|
|
const columnStats = reactive<Map<string, ColumnHeightStats>>(new Map());
|
||
|
|
|
||
|
|
// Pending samples not yet incorporated
|
||
|
|
const pendingSamples = ref<HeightSample[]>([]);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Record a measured height for a specific column's cell
|
||
|
|
*/
|
||
|
|
function recordHeight(columnKey: string, height: number) {
|
||
|
|
pendingSamples.value.push({
|
||
|
|
columnKey,
|
||
|
|
height,
|
||
|
|
timestamp: Date.now(),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Flush pending samples and update running averages
|
||
|
|
* Returns true if any column's average changed significantly
|
||
|
|
*/
|
||
|
|
function flushSamples(): boolean {
|
||
|
|
if (pendingSamples.value.length === 0) return false;
|
||
|
|
|
||
|
|
const changed: string[] = [];
|
||
|
|
|
||
|
|
// Group samples by column
|
||
|
|
const grouped = new Map<string, number[]>();
|
||
|
|
for (const sample of pendingSamples.value) {
|
||
|
|
const existing = grouped.get(sample.columnKey) || [];
|
||
|
|
existing.push(sample.height);
|
||
|
|
grouped.set(sample.columnKey, existing);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update stats for each column
|
||
|
|
for (const [columnKey, heights] of grouped) {
|
||
|
|
let stats = columnStats.get(columnKey);
|
||
|
|
|
||
|
|
if (!stats) {
|
||
|
|
stats = {
|
||
|
|
columnKey,
|
||
|
|
samples: [],
|
||
|
|
average: 0,
|
||
|
|
count: 0,
|
||
|
|
};
|
||
|
|
columnStats.set(columnKey, stats);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add new samples
|
||
|
|
stats.samples.push(...heights);
|
||
|
|
stats.count += heights.length;
|
||
|
|
|
||
|
|
// Keep only last 20 samples per column for running average
|
||
|
|
if (stats.samples.length > 20) {
|
||
|
|
stats.samples = stats.samples.slice(-20);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Recompute average
|
||
|
|
const newAverage =
|
||
|
|
stats.samples.reduce((sum, h) => sum + h, 0) / stats.samples.length;
|
||
|
|
|
||
|
|
// Check if change is significant
|
||
|
|
if (stats.count >= SAMPLE_THRESHOLD) {
|
||
|
|
const oldAverage = stats.average;
|
||
|
|
if (oldAverage > 0 && Math.abs(newAverage - oldAverage) / oldAverage > RECOMPUTE_THRESHOLD) {
|
||
|
|
changed.push(columnKey);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
stats.average = newAverage;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear pending
|
||
|
|
pendingSamples.value = [];
|
||
|
|
|
||
|
|
return changed.length > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the current estimated height for a column
|
||
|
|
*/
|
||
|
|
function getColumnHeight(columnKey: string): number {
|
||
|
|
const stats = columnStats.get(columnKey);
|
||
|
|
return stats?.average ?? 44; // Default fallback
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all column heights as a map
|
||
|
|
*/
|
||
|
|
function getAllColumnHeights(): Map<string, number> {
|
||
|
|
const result = new Map<string, number>();
|
||
|
|
for (const [key, stats] of columnStats) {
|
||
|
|
result.set(key, stats.average || 44);
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reset stats for a column
|
||
|
|
*/
|
||
|
|
function resetColumn(columnKey: string) {
|
||
|
|
columnStats.delete(columnKey);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reset all stats
|
||
|
|
*/
|
||
|
|
function resetAll() {
|
||
|
|
columnStats.clear();
|
||
|
|
pendingSamples.value = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
columnStats,
|
||
|
|
pendingSamples,
|
||
|
|
recordHeight,
|
||
|
|
flushSamples,
|
||
|
|
getColumnHeight,
|
||
|
|
getAllColumnHeights,
|
||
|
|
resetColumn,
|
||
|
|
resetAll,
|
||
|
|
};
|
||
|
|
}
|