Fit the row-label column to the labels it actually shows
Values fitted and row labels did not. The measurement pairs each column's
header with its body cell, and for the row-header columns the header is a
blank corner cell — so the column was being sized from an empty string,
never from the labels beneath it.
Measured with a canvas instead of the DOM, for the same reason the headers
cannot size themselves: the cell is clipped, so reading its box back returns
the width it was allotted rather than the width of its text. Tree
indentation is added, since it occupies real width. The result is pinned as
a column_size_override on __ROW_PATH__ — the key
restore_column_size_overrides special-cases for this column, and an override
is what survives subsequent draws, which is precisely why overrides were
defeating resetAutoSize earlier.
Group headers are deliberately left alone. pro.css is explicit:
/* Header groups should overflow and not contribute to auto-sizing. */
thead tr:not(.rt-autosize) th { overflow: hidden; max-width: 0px; }
Only the leaf header row participates, because a group label spans many
columns and letting it set width would widen all of them. With the ordinal
prefix in front, "01 - Prior…" still reads when truncated. Fixing it
properly means distributing a group's label width across its span, which is
a different job.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
194134ea5f
commit
b75ce939b1
@ -955,21 +955,25 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
|
|
||||||
// Size every column to its contents.
|
// Size every column to its contents.
|
||||||
//
|
//
|
||||||
// The datagrid measures content on its own -- draw() calls
|
// Values fit on their own: draw() calls regular_table.resetAutoSize(), which
|
||||||
// regular_table.resetAutoSize() when _reset_column_size is set -- but the reset
|
// clears the width caches so the next draw measures cells and sets each
|
||||||
// is immediately undone:
|
// column's min-width from the result. The complication is that the plugin's own
|
||||||
|
// draw saves the *live* widths first and restores them immediately after the
|
||||||
|
// reset, so going through it preserves whatever the columns already are. Hence
|
||||||
|
// driving regular_table directly.
|
||||||
//
|
//
|
||||||
// const old_sizes = save_column_size_overrides.call(this); // live widths
|
// Two things that measurement will never fix:
|
||||||
// ... if (this._reset_column_size) { resetAutoSize() }
|
|
||||||
// restore_column_size_overrides.call(this, old_sizes); // straight back
|
|
||||||
//
|
//
|
||||||
// and old_sizes comes from regular_table.saveColumnSizes(), the *live* widths,
|
// - Group headers are excluded on purpose. pro.css says so:
|
||||||
// not from plugin_config. So every draw preserves whatever the columns are
|
// /* Header groups should overflow and not contribute to auto-sizing. */
|
||||||
// currently at, and clearing the config releases nothing. Setting the flag and
|
// thead tr:not(.rt-autosize) th { overflow: hidden; max-width: 0px; }
|
||||||
// going through the plugin's draw cannot work for the same reason.
|
// Only the leaf header row participates. Letting a group label set width
|
||||||
|
// would widen every column beneath it, so this is left alone -- with the
|
||||||
|
// ordinal prefix in front, "01 - Prior…" still reads when truncated.
|
||||||
//
|
//
|
||||||
// So drive regular_table directly: drop the cached sizes, reset, and draw. It is
|
// - The row-label column measures its *header*, which for row headers is a
|
||||||
// the same draw the plugin calls, without the save/restore wrapped around it.
|
// blank corner cell, so it never reflects the labels underneath. That one
|
||||||
|
// is worth fixing, and is what fitRowLabels does below.
|
||||||
async function fitColumns() {
|
async function fitColumns() {
|
||||||
const viewer = viewerRef.current
|
const viewer = viewerRef.current
|
||||||
if (!viewer) return
|
if (!viewer) return
|
||||||
@ -987,6 +991,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
grid.resetAutoSize()
|
grid.resetAutoSize()
|
||||||
await grid.draw({ invalid_columns: true })
|
await grid.draw({ invalid_columns: true })
|
||||||
|
|
||||||
|
await fitRowLabels(viewer, grid)
|
||||||
|
|
||||||
// Widths are not part of ViewConfig, so persist the layout to keep the
|
// Widths are not part of ViewConfig, so persist the layout to keep the
|
||||||
// saved copy in step with what is on screen.
|
// saved copy in step with what is on screen.
|
||||||
const cfg = await captureConfig()
|
const cfg = await captureConfig()
|
||||||
@ -997,21 +1003,49 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In rollup mode the datagrid emits a grand-total column group as well as the
|
// Pin the row-label column wide enough for the labels it shows.
|
||||||
// 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
|
|
||||||
// Prior Year / Plan / Forecast that sum is meaningless and reads as a real
|
|
||||||
// figure to anyone scanning the sheet.
|
|
||||||
//
|
//
|
||||||
// The engine cannot separate them: t_totals is { BEFORE, HIDDEN, AFTER }, and in
|
// Measured with a canvas rather than the DOM: the cell is clipped, so reading
|
||||||
// a rollup the grand total *is* the root of the hierarchy that produces the
|
// its box back gives the width it was allotted, not the width of its text --
|
||||||
// subtotals. So it is hidden rather than suppressed -- the view still computes
|
// the same circularity that stops headers sizing themselves.
|
||||||
// it, it is simply not painted.
|
|
||||||
//
|
//
|
||||||
// psp-split-total marks it and psp-split-subtotal marks the subtotals, so this
|
// __ROW_PATH__ is the key restore_column_size_overrides special-cases for this
|
||||||
// survives adding measures or rearranging the pivot, unlike hiding by position.
|
// column, and an override is what survives every subsequent draw, which is
|
||||||
// The rule has to live inside the plugin's shadow root; a document stylesheet
|
// exactly why overrides were defeating resetAutoSize in the first place.
|
||||||
// cannot reach it.
|
async function fitRowLabels(viewer, grid) {
|
||||||
|
const cells = [...grid.querySelectorAll('tbody th')]
|
||||||
|
if (cells.length === 0) return
|
||||||
|
|
||||||
|
const probe = cells[0]
|
||||||
|
const style = getComputedStyle(probe)
|
||||||
|
const ctx = (fitRowLabels._ctx ||= document.createElement('canvas').getContext('2d'))
|
||||||
|
ctx.font = `${style.fontWeight} ${style.fontSize} ${style.fontFamily}`
|
||||||
|
|
||||||
|
// Indentation is real width: a tree label sits inside a flex container with
|
||||||
|
// the expand control beside it, so measure from the cell's left edge.
|
||||||
|
const padding = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)
|
||||||
|
let widest = 0
|
||||||
|
for (const cell of cells) {
|
||||||
|
const text = (cell.textContent || '').trim()
|
||||||
|
if (!text) continue
|
||||||
|
const indent = cell.querySelector('span.rt-tree-container')
|
||||||
|
? (parseFloat(getComputedStyle(cell.firstElementChild).paddingLeft) || 0)
|
||||||
|
: 0
|
||||||
|
widest = Math.max(widest, ctx.measureText(text).width + indent)
|
||||||
|
}
|
||||||
|
if (widest === 0) return
|
||||||
|
|
||||||
|
// A few px over, since canvas metrics and rendered text differ slightly with
|
||||||
|
// font fallback and letter-spacing.
|
||||||
|
const width = Math.ceil(widest + padding + 8)
|
||||||
|
|
||||||
|
const { table: _t, ...cfg } = await viewer.save()
|
||||||
|
const pc = { ...(cfg.plugin_config || {}) }
|
||||||
|
pc.columns = { ...(pc.columns || {}) }
|
||||||
|
pc.columns.__ROW_PATH__ = { ...(pc.columns.__ROW_PATH__ || {}), column_size_override: width }
|
||||||
|
await viewer.restore({ ...cfg, plugin_config: pc })
|
||||||
|
}
|
||||||
|
|
||||||
const GRID_CSS = `
|
const GRID_CSS = `
|
||||||
/* Let headers and row labels size their own column.
|
/* Let headers and row labels size their own column.
|
||||||
*
|
*
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user