Add per-table "hide chrome/headers when unfocused" clean-look option

New Settings checkbox: while a table isn't the selected canvas object,
its title bar, column letters and row numbers disappear entirely -- not
just visually, their reserved space collapses too, so the table shrinks
to just its cell grid. Clicking back into it (e.g. any cell) reselects
it and brings everything back.

TableObject.hideChromeUnfocused (store/types.ts) + setTableHideChromeUnfocused
(document.ts) + io/json.ts normalization, following the existing
per-object appearance-setting pattern. CanvasObject.vue gates its title
bar on a new showChrome computed and passes its existing `selected` prop
down to TableGrid.vue (previously only took tableId) so it can make the
same call for its own headers.

TableGrid.vue's HEAD_W (a plain constant) becomes a `headW` computed,
and the existing `headerH` computed now returns 0 for both whenever
hidden -- sufficient on its own since rowTop()/colLeft() already sum
those two values as their base offset and are the single source every
other position calculation in the file (sticky-frozen offsets, the
in-cell editor overlay, clip/fill marquees, scroll-into-view, row
virtualization) already goes through.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-10 00:16:10 +02:00
co-authored by Claude Sonnet 5
parent c26b1dd857
commit 7646162767
8 changed files with 101 additions and 14 deletions
+30
View File
@@ -2650,6 +2650,34 @@ describe('per-object background & text colour', () => {
});
});
describe('table "hide chrome when unfocused" (clean-look presentation option)', () => {
it('toggles hideChromeUnfocused and clears the field when turned off', () => {
const s = fresh();
const id = tableId(s, 'Umsatz');
expect(s.tableById(id)!.hideChromeUnfocused).toBeUndefined();
s.setTableHideChromeUnfocused(id, true);
expect(s.tableById(id)!.hideChromeUnfocused).toBe(true);
s.setTableHideChromeUnfocused(id, false);
expect(s.tableById(id)!.hideChromeUnfocused).toBeUndefined(); // "unset", not stored as false
});
it('survives a JSON round-trip; a non-boolean value on import is dropped', () => {
const s = fresh();
const id = tableId(s, 'Umsatz');
s.setTableHideChromeUnfocused(id, true);
const before = s.snapshot();
s.replaceDocument(parseDocument(serializeDocument(before)));
expect(s.snapshot()).toEqual(before);
expect(s.tableById(id)!.hideChromeUnfocused).toBe(true);
const doc = JSON.parse(serializeDocument(s.snapshot()));
const tbl = doc.sheets[0].objects.find((o: { id: string }) => o.id === id);
tbl.hideChromeUnfocused = 'yes';
s.replaceDocument(parseDocument(JSON.stringify(doc)));
expect(s.tableById(id)!.hideChromeUnfocused).toBeUndefined();
});
});
describe('full-fidelity JSON export — backgrounds, formats, sheet settings', () => {
/** Populate *every* persisted setting, then verify the export string carries
* it and a round-trip is byte-for-byte identical. */
@@ -2678,6 +2706,7 @@ describe('full-fidelity JSON export — backgrounds, formats, sheet settings', (
s.setObjectLocale(umsatzId, 'fr-FR');
s.setTableView(umsatzId, { mode: 'fixed', width: 520, height: 300, frozenRows: 1, frozenCols: 1 });
s.setTableRowHeight(umsatzId, 30);
s.setTableHideChromeUnfocused(umsatzId, true);
s.setRowHeight(umsatzId, 4, 52);
s.setColWidth(umsatzId, 0, 130);
s.setRangeFormat(umsatzId, { col: 1, row: 1 }, { col: 3, row: 4 }, {
@@ -2751,6 +2780,7 @@ describe('full-fidelity JSON export — backgrounds, formats, sheet settings', (
rowHeights: { '4': 52 },
colWidths: { '0': 130 },
view: { mode: 'fixed', width: 520, height: 300, frozenRows: 1, frozenCols: 1 },
hideChromeUnfocused: true,
});
expect(tbl.formats['1,1']).toEqual({
align: 'right',