Add a Fit action to size columns to their contents

The datagrid already measures content -- draw() calls
regular_table.resetAutoSize() -- but only when _reset_column_size is set,
and it puts any pinned widths back immediately afterwards:

    const old_sizes = save_column_size_overrides.call(this);
    ... if (this._reset_column_size) { resetAutoSize() }
    restore_column_size_overrides.call(this, old_sizes);

So a width pinned by dragging a column edge, or carried in a saved layout,
outlives every measurement -- including the automatic ones the datagrid does
when split_by or columns change. Clearing the overrides is what releases
them; the flag then makes the next draw measure instead of reuse.

The flag is set after restore(), not before: building the model recomputes
it from what changed in the config, and would clear it again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-17 13:52:18 -04:00
parent 7aaf017532
commit 7975acc0fc

View File

@ -948,6 +948,48 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
} }
} }
// Size every column to its contents.
//
// The datagrid already does this -- draw() calls regular_table.resetAutoSize()
// -- but only when _reset_column_size is set, and it restores any pinned widths
// immediately afterwards:
//
// const old_sizes = save_column_size_overrides.call(this);
// ... if (this._reset_column_size) { resetAutoSize() }
// restore_column_size_overrides.call(this, old_sizes);
//
// So a width pinned by dragging, or carried in a saved layout, wins over the
// measurement for good. Clearing the overrides is what actually releases them;
// the flag then makes the next draw measure rather than reuse.
//
// Order matters: the flag is set *after* restore(), because building the model
// recomputes it from what changed in the config and would clear it again.
async function fitColumns() {
const viewer = viewerRef.current
if (!viewer) return
try {
const { table: _t, ...cfg } = await viewer.save()
const pc = { ...(cfg.plugin_config || {}) }
if (pc.columns) {
pc.columns = Object.fromEntries(
Object.entries(pc.columns).map(([col, v]) => {
const { column_size_override, ...rest } = v || {}
return [col, rest]
})
)
}
await viewer.restore({ ...cfg, plugin_config: pc })
const plugin = await viewer.getPlugin()
if (plugin) plugin._reset_column_size = true
const view = await viewer.getView()
await plugin?.draw?.(view)
} catch (err) {
console.error('[fitColumns]', err)
flash(err.message || String(err), 'error')
}
}
// In rollup mode the datagrid emits a grand-total column group as well as the // In rollup mode the datagrid emits a grand-total column group as well as the
// subtotals. The subtotals are the point -- they are what per-branch collapse // subtotals. The subtotals are the point -- they are what per-branch collapse
// needs -- but the grand total sums across the split, and when the split is // needs -- but the grand total sums across the split, and when the split is
@ -1517,6 +1559,15 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
<div className="w-px h-4 bg-gray-200 shrink-0" /> <div className="w-px h-4 bg-gray-200 shrink-0" />
<button onClick={fitColumns}
title="Size every column to its contents, releasing any widths pinned by dragging or carried in a saved layout"
className="border border-gray-200 rounded px-1.5 py-0.5 text-gray-500 hover:border-gray-400
transition-colors whitespace-nowrap">
Fit
</button>
<div className="w-px h-4 bg-gray-200 shrink-0" />
{/* Expand group */} {/* Expand group */}
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-gray-400 uppercase tracking-wide" style={{fontSize:'10px'}}>Expand</span> <span className="text-gray-400 uppercase tracking-wide" style={{fontSize:'10px'}}>Expand</span>