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.
149 lines
3.6 KiB
149 lines
3.6 KiB
/** |
|
* 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, |
|
}; |
|
}
|
|
|