Show the selected range, not just the active cell, in status/formula bar

New store computed selectionRangeName: for a single cell it's identical
to activeCellName ("Umsatz::B2"), for a multi-cell selection it shows
the full primary rectangle ("Umsatz::B2:E6"), and a discontiguous
multi-selection gets a "+N" suffix for the further rectangles.
activeCellName itself is untouched (still anchor-only, since that's what
a formula/edit actually targets) -- only the formula bar's name field and
the status bar's range label switch to the new computed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-02 15:49:35 +02:00
co-authored by Claude Sonnet 5
parent 85a8687020
commit 1046b22e96
5 changed files with 50 additions and 6 deletions
+8 -3
View File
@@ -13,7 +13,7 @@ Vorbild von **Apple Numbers**. Zwei Dinge stehen im Mittelpunkt:
```bash ```bash
npm install npm install
npm run dev # Vite-Devserver npm run dev # Vite-Devserver
npm run test # Vitest-Suite (Engine + Store + IO), 178 Tests npm run test # Vitest-Suite (Engine + Store + IO), 179 Tests
npm run build # Typecheck + Produktionsbuild npm run build # Typecheck + Produktionsbuild
npm run lint # ESLint (flat config) npm run lint # ESLint (flat config)
``` ```
@@ -134,7 +134,12 @@ Zeilenhöhe der Tabelle geklemmt.
— dadurch **umbricht** eine lange Formel statt seitwärts wegzuscrollen, und — dadurch **umbricht** eine lange Formel statt seitwärts wegzuscrollen, und
an der unteren Kante lässt sie sich per **Maus** (natives `resize: vertical`) an der unteren Kante lässt sie sich per **Maus** (natives `resize: vertical`)
auf beliebig viele Zeilen aufziehen, um mehr auf einmal zu sehen. `Alt`+`Enter` auf beliebig viele Zeilen aufziehen, um mehr auf einmal zu sehen. `Alt`+`Enter`
bzw. `Cmd`+`Enter`, `F4` und `Esc` verhalten sich wie im In-Zellen-Editor. bzw. `Cmd`+`Enter`, `F4` und `Esc` verhalten sich wie im In-Zellen-Editor. Das
Namensfeld links davon (ebenso das Pendant links in der **Statuszeile**, die
mit den Blatt-Reitern eine gemeinsame Zeile am unteren Rand teilt) zeigt bei
einer Mehrfachauswahl nicht nur die aktive Zelle, sondern den ganzen markierten
**Bereich** (`Umsatz::B2:E6`), bei einer diskontinuierlichen Auswahl zusätzlich
`+N` für die weiteren Rechtecke.
**Bezüge per Maus einfügen ("Point-Mode")**: sobald der Editor (Zelle oder **Bezüge per Maus einfügen ("Point-Mode")**: sobald der Editor (Zelle oder
Formelleiste) eine Formel (`=…`) enthält, fügt ein **Klick** auf eine Zelle — Formelleiste) eine Formel (`=…`) enthält, fügt ein **Klick** auf eine Zelle —
@@ -401,7 +406,7 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
## Tests ## Tests
`npm run test` — Vitest, 178 Tests: `npm run test` — Vitest, 179 Tests:
- **lexer / parser** — Präzedenz & Assoziativität (`^` vor `*/` vor `+-`, `^` - **lexer / parser** — Präzedenz & Assoziativität (`^` vor `*/` vor `+-`, `^`
rechts, `-2^2 = -(2^2)`), lokale/qualifizierte/quoted Bezüge, `!` **und** `::`, rechts, `-2^2 = -(2^2)`), lokale/qualifizierte/quoted Bezüge, `!` **und** `::`,
+2 -2
View File
@@ -108,8 +108,8 @@ function onKeydown(e: KeyboardEvent) {
<template> <template>
<div class="formula-bar"> <div class="formula-bar">
<span class="cell-name" :title="store.activeCellName.value"> <span class="cell-name" :title="store.selectionRangeName.value">
{{ store.activeCellName.value }} {{ store.selectionRangeName.value }}
</span> </span>
<span class="fx">fx</span> <span class="fx">fx</span>
<textarea <textarea
+1 -1
View File
@@ -11,7 +11,7 @@ const fmt = (n: number) =>
<template> <template>
<div class="status-bar"> <div class="status-bar">
<span class="range">{{ store.activeCellName.value }}</span> <span class="range">{{ store.selectionRangeName.value }}</span>
<span v-if="agg.count > 0" class="metric">Summe <b>{{ fmt(agg.sum) }}</b></span> <span v-if="agg.count > 0" class="metric">Summe <b>{{ fmt(agg.sum) }}</b></span>
<span v-if="agg.count > 0" class="metric">Mittel <b>{{ fmt(agg.avg) }}</b></span> <span v-if="agg.count > 0" class="metric">Mittel <b>{{ fmt(agg.avg) }}</b></span>
<span class="metric">Anzahl <b>{{ agg.count }}</b></span> <span class="metric">Anzahl <b>{{ agg.count }}</b></span>
+16
View File
@@ -138,6 +138,22 @@ describe('seed document acceptance (canvas model)', () => {
// the active cell is the anchor -- the first cell of the selection -- not // the active cell is the anchor -- the first cell of the selection -- not
// wherever the selection was extended to. // wherever the selection was extended to.
expect(s.activeCellName.value).toBe(`Umsatz::${toA1(1, 1)}`); expect(s.activeCellName.value).toBe(`Umsatz::${toA1(1, 1)}`);
// selectionRangeName shows the full range instead (status bar / formula bar)
expect(s.selectionRangeName.value).toBe(`Umsatz::${toA1(1, 1)}:${toA1(1, 5)}`);
});
it('selectionRangeName shows a single address for a 1x1 selection and a "+N" suffix for a discontiguous multi-selection', () => {
const s = fresh();
const umsatz = tableId(s, 'Umsatz');
s.setSelection({ tableId: umsatz, anchor: { col: 0, row: 0 }, focus: { col: 0, row: 0 } });
expect(s.selectionRangeName.value).toBe(`Umsatz::${toA1(0, 0)}`);
s.addToSelection(umsatz, 3, 3);
s.addToSelection(umsatz, 5, 5);
// addToSelection() moves the primary rect to the just-clicked cell (see
// "Mehrfachauswahl" tests below) -- the range name follows it, plus a
// "+N" for the other rectangles now in the selection.
expect(s.selectionRangeName.value).toBe(`Umsatz::${toA1(5, 5)} +2`);
}); });
it('recalculation visits precedents before dependents across tables', () => { it('recalculation visits precedents before dependents across tables', () => {
+23
View File
@@ -1412,6 +1412,28 @@ function createStore() {
return `${t?.name ?? '—'}::${toA1(cell.col, cell.row)}`; return `${t?.name ?? '—'}::${toA1(cell.col, cell.row)}`;
}); });
/** Like {@link activeCellName}, but for a multi-cell selection it shows the
* full range (`Umsatz::B2:E6`) instead of just the anchor -- used wherever
* the UI shows "where am I" (formula bar, status bar), as opposed to
* `activeCellName`/`activeCell`, which stay anchor-only because that's
* what a formula/edit actually targets (see the "selection aggregate &
* cell name are live" test). A discontiguous multi-selection additionally
* gets a `+N` suffix for the further rectangles beyond the primary one. */
const selectionRangeName = computed(() => {
const sel = doc.selection;
const t = sel ? tableById(sel.tableId) : activeTable.value;
const tableName = t?.name ?? '—';
if (!sel) return `${tableName}::${toA1(0, 0)}`;
const rects = selectionRects(sel);
const [p] = rects;
const addr =
p.c0 === p.c1 && p.r0 === p.r1
? toA1(p.c0, p.r0)
: `${toA1(p.c0, p.r0)}:${toA1(p.c1, p.r1)}`;
const extra = rects.length > 1 ? ` +${rects.length - 1}` : '';
return `${tableName}::${addr}${extra}`;
});
const activeCellInput = computed(() => { const activeCellInput = computed(() => {
const sel = doc.selection; const sel = doc.selection;
return sel ? cellInput(sel.tableId, sel.anchor.col, sel.anchor.row) : ''; return sel ? cellInput(sel.tableId, sel.anchor.col, sel.anchor.row) : '';
@@ -1606,6 +1628,7 @@ function createStore() {
selectionRects, selectionRects,
activeCell, activeCell,
activeCellName, activeCellName,
selectionRangeName,
activeCellInput, activeCellInput,
selectionAggregate, selectionAggregate,
// clipboard // clipboard