Erster Druck markiert den zusammenhängenden Bereich um die aktive Zelle
(Excel-Verhalten, iterative Rechteck-Expansion bis zur ersten leeren Zeile/
Spalte oder dem Tabellenrand); ein zweiter, unmittelbar folgender Druck
erweitert auf alle befüllten Zellen der Tabelle. Der Zyklus erkennt sich
selbst über einen Abgleich der aktuellen Auswahl mit der zuletzt gesetzten
und setzt sich bei jeder anderen Aktion dazwischen zurück.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rechtsklick auf eine Zelle → "🕸 Abhängigkeiten anzeigen" öffnet ein an der
rechten Seite angedocktes Panel (wie das Einstellungen-Panel links) mit den
direkten Vorgängern (was die Zelle liest) und Nachfolgern (was sie liest),
auch über Tabellen-/Blattgrenzen hinweg. Ein Klick auf einen Eintrag
navigiert wirklich dorthin: wählt die echte Zelle im Dokument aus, wechselt
bei Bedarf das Blatt-Tab, und das Panel zentriert sich neu — Blatt-Tabs,
Canvas, Formel- und Statusleiste bleiben dabei die ganze Zeit sichtbar und
bedienbar (kein Vollbild-Dialog). Jeder Eintrag zeigt immer den Blattnamen,
nie nur implizit. Zirkelbezüge werden rot markiert.
Neu in der Engine: Workbook.graphPrecedents/graphDependents, dünne
öffentliche Wrapper um den bereits vorhandenen DependencyGraph. Im Store:
cellGraphSelf/cellGraphNeighbors (Anreicherung für die Anzeige) sowie
openDependencyGraph/closeDependencyGraph (dokumentweiter UI-State, ruft
intern das bestehende selectCell auf).
424 Tests (Engine-/Store-Ebene vollständig abgedeckt), vue-tsc/eslint/build
sauber. Das Panel selbst nur live per Playwright verifiziert (kein
Komponenten-Test-Setup vorhanden, in README dokumentiert).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- AND(...)/OR(...)/NOT(...) als gewöhnliche Funktionen (kein Kurzschluss-
Sonderfall wie IF/XLOOKUP nötig, werten wie in echtem Excel alle
Argumente eager aus). Dabei die bisher dreifach duplizierte
Wahrheits-Prüfung zu functions.ts#isTruthy() zusammengezogen, jetzt
von FUNCTIONS.IF, evalIf() und dem Block-IF-Statement gemeinsam genutzt.
Schließt "kein AND/OR für zusammengesetzte Bedingungen" aus den
Bekannten Grenzen, auch nutzbar in "Mit Schleife"-IF-Bedingungen.
- Neue Grammatik-Ergänzung: ErrorLiteralNode. Jeder der sieben Fehlercodes
(#REF, #DIV/0!, #NAME?, #ERR, #CIRC, #VALUE!, #N/A) lässt sich jetzt
direkt in eine Formel tippen (=IF(A1=0;#N/A;A1)) und wertet unbedingt zu
genau diesem Fehler aus.
- Zeilen/Spalten einfügen/löschen mitten im Gitter (Rechtsklick auf einen
Zeilen-/Spaltenkopf), nicht nur am Rand wie bisher: Zellinhalte, Formate
und Zeilenhöhen/Spaltenbreiten verschieben sich innerhalb der Tabelle,
und jede Formel im gesamten Dokument, die die Tabelle referenziert (auch
über Tabelle!Zelle/Blatt!Tabelle::Zelle-Qualifier aus anderen Tabellen),
wird automatisch mitgezogen. Ein Bereich, der die Stelle überspannt,
wächst/schrumpft entsprechend; eine Referenz, die exakt die gelöschte
Zeile/Spalte trifft, wird zu einem echten #REF-Literal im Formeltext
(nutzt die neue ErrorLiteralNode-Grammatik). $-Sperren bleiben dabei
bewusst wirkungslos (anders als beim Kopieren), unveränderte Referenzen
bleiben byte-identisch. Token-Level-Splice wie shiftFormula (Kopieren/
Ausschneiden), keine AST-Neuserialisierung.
406 Tests, vue-tsc/eslint/build sauber, Playwright-live-verifiziert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Beim Tippen eines Funktionsnamens (nach "=", einem Operator, "(" oder
";" -- nicht bei einem Zellbezug wie A1) zeigt sowohl die Formelleiste
als auch der In-Zellen-Editor jetzt ein Dropdown mit passenden
eingebauten UND eigenen Funktionen samt Kurzsignatur. ↑/↓ navigieren,
Tab/Enter/Klick übernehmen (fügt "NAME(" ein), Esc schließt nur das
Dropdown, ohne die Bearbeitung abzubrechen.
- engine/refs.ts: neue reine Funktion currentIdentifierAtCaret() --
strukturell wie das schon vorhandene cycleReferenceLocks (=
abstreifen, tokenize, am Cursor nachsehen), aber nur der Text bis
zum Cursor wird tokenisiert und nur das letzte Token zählt; ein
IDENT, das bis an den Cursor reicht und nicht wie ein Zellbezug
aussieht, ist der Funktionsname. tokenize() hängt ein EOF-Token an
-- muss beim Suchen des "letzten Tokens" herausgefiltert werden
(von den eigenen Tests aufgedeckt).
- engine/functions.ts: BUILTIN_SIGNATURES (Name -> Kurzsignatur je
eingebauter Funktion), gegen FUNCTIONS' Keys testabgesichert, damit
eine künftige neue Funktion ohne Eintrag hier nicht still im
Dropdown fehlt.
- store/document.ts: functionSuggestions(prefix) kombiniert eingebaute
+ dokumenteigene Funktionen, case-insensitiv gefiltert, sortiert.
- composables/useFormulaAutocomplete.ts: pro Editor-Instanz eigener
State (suggestions/activeIndex), orchestriert die drei Bausteine
oben; die Übernahme eines Vorschlags läuft über dieselbe
replace(start,end,value)-Mechanik, die Point-Mode für den Bezug-
Einfügeklick schon nutzt -- keine neue Einfüge-Infrastruktur nötig.
- components/FormulaSuggest.vue: geteiltes, rein präsentationelles
Dropdown (position:absolute; top:100% relativ zum Editor-Wrapper,
kein Caret-Pixel-Tracking).
- FormulaBar.vue + TableGrid.vue: gleiches Einbindungsmuster in
beiden Editoren (Wrapper bekommt position:relative, onInput ruft
autocomplete.update(), onKeydown fängt Pfeiltasten/Tab/Enter/Esc
vor der bestehenden Logik ab, wenn Vorschläge offen sind, onBlur
schließt das Dropdown).
Tests: 13 neue (refs.spec.ts: 7 Fälle für currentIdentifierAtCaret;
functions.spec.ts neu: Signatur-Tabelle deckungsgleich mit FUNCTIONS;
document.spec.ts: functionSuggestions Filter/Mischung/Sortierung).
318 -> 331 Tests, alle grün. vue-tsc/eslint/build sauber. Live per
Playwright verifiziert in Formelleiste UND In-Zellen-Editor: Filterung,
Pfeiltasten-Navigation, Tab-Übernahme, Maus-Klick-Übernahme, Esc
schließt nur das Dropdown, eigene Funktion (MARGE) erscheint neben
eingebauten (MAX) korrekt einsortiert.
READMEs aktualisiert (neuer Abschnitt "Funktions-Autovervollständigung",
Tastaturtabelle, Testliste, Bekannte Grenzen -- dabei eine veraltete
Aussage korrigiert, die Autovervollständigung für benutzerdefinierte
Funktionen noch als "bewusst fehlend" auflistete).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Excel/Numbers-style reference picking: while editing a formula (buffer
starts with "="), a click on any grid cell -- in the same table or a
completely different one -- inserts its reference at the caret instead of
changing the selection; a drag inserts/grows a range reference live as you
drag. A further click/drag before typing anything replaces the just-inserted
reference; typing anything (";" to separate SUM() arguments included, but
not special-cased -- any character ends the window, same as Excel) makes the
next click append a fresh one instead.
- document.ts: whichever editor is open (in-cell editor or formula bar)
registers a small handle (home tableId + read/replace the buffer);
pointClick()/pointDrag() insert or replace a reference into it, qualifying
with `Name!` (quoted if needed) when the clicked table differs from the
editor's own. formulaPointModeActive() gates it on the buffer being a
formula; noteFormulaTyped() (wired to real typing, not the programmatic
inserts) ends the "replace" window.
- TableGrid.vue: onCellMouseDown checks point mode before its normal
selection/edit handling, tracking a drag the same way normal selection
does; dblclick during point mode no longer hijacks into editing that cell.
- FormulaBar.vue: registers on focus, same click/drag targeting works from
there without disturbing the grid's own selection.
Verified interactively against the dev server with Playwright, including a
real bug the store-level tests (a mocked editor handle, no real DOM) missed
entirely: preventDefault() alone kept focus on the editor, but the mousedown
still bubbled to CanvasObject.vue's own handler, which selects/brings the
clicked table to front -- closing the very editor being pointed into. Fixed
with stopPropagation() alongside it. After that fix: same-table and
cross-table click/drag both insert correctly-qualified references, a second
click replaces before typing and appends after, and the live selection/
active object are untouched by a point-mode click.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Snapshot-based, like the existing JSON export/import round-trip
(snapshot()/replaceDocument()) rather than a patch/command log -- simple and
provably correct for a document this size.
- document.ts: a deep watch on `doc` tracks a "content fingerprint" (sheets +
locale + csvDelimiter, deliberately excluding selection/activeObjectId, so
moving the cursor is never an undo step) and debounces commits by 150ms --
long enough to coalesce a resize-drag's per-mousemove calls into one step,
short enough that two genuinely separate quick actions don't merge. A new
edit after an undo immediately drops the redo branch (not debounced, so a
bound button is never briefly wrong). undo()/redo() flush any pending
change first, then pop/apply a snapshot via a new applyDocumentState() core
that replaceDocument() also uses -- replaceDocument() (JSON import
"replace", "reset to seed") additionally resets history, since loading a
different document shouldn't leave the old one undoable into it; undo/redo
themselves must not (that's the very stack they're unwinding).
- TableGrid.vue: Strg/Cmd+Z undoes, Strg/Cmd+Y or +Shift+Z redoes, deferring
to a cell editor's native undo while one is open (the existing `if
(editing.value) return` guard already covers this).
- App.vue: a window-level fallback for when no table has focus, skipped
whenever focus sits in any text field so their own native undo keeps
working.
- AppToolbar.vue: ↶/↷ buttons, disabled via store.canUndo/canRedo.
Verified interactively against the dev server with Playwright: the toolbar
button enables the instant a change happens (not after the debounce);
separately-timed edits each undo as their own step; a multi-tick resize drag
undoes in one step back to the original width; Ctrl+Z, Ctrl+Y and the
toolbar buttons all work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
macOS's Option key is a compose key (used to type accented/special
characters), which can make it unreliable as a plain modifier in some
browser/layout combinations -- Alt+Enter alone wasn't reaching the handler
for this user on Mac. Microsoft's own Mac Excel shortcut for this is
inconsistent across versions too (Control+Option+Return traditionally,
plain Option+Return only in 365).
- onEditKeydown now accepts Enter with either altKey OR metaKey (Cmd),
giving a Mac-safe alternative that doesn't depend on Option's compose
behavior.
- Also checks e.code ('Enter'/'NumpadEnter') alongside e.key, in case
composition ever alters what .key reports for the combination (.code
reflects the physical key regardless).
Verified interactively: Alt+Enter, Cmd+Enter, and plain Enter (still
commits normally) all produce the expected value/behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1. In-cell editor width/height ("nur in der Breite der Zelle angezeigt"):
the editor now lives as an overlay (like the clip/fill markers) instead
of inside its <td>, so it can grow past the cell -- width first, up to
an approximated display-width cap, then wrapping and growing height.
Sized via a hidden mirror element (same font/padding, real DOM
measurement -- a <textarea> can't self-size to content with CSS alone).
2. F4 now cycles every reference a real text selection overlaps, each from
its own lock state, not just the one at the caret -- select a stretch of
formula spanning several references and F4 adjusts all of them at once,
same as Excel. cycleReferenceLock(formula, caret) -> cycleReferenceLocks
(formula, start, end); a collapsed selection (start === end) keeps the
old single-reference caret behaviour. Wired into both the in-cell editor
and the formula bar.
3. Alt+Enter inserts a literal newline in the editor instead of committing
(explicit splice at the caret, not relying on the textarea's native
Enter handling, which isn't consistently reachable for Alt+Enter). A
cell holding a newline displays it wrapped (.cell.multiline) instead of
collapsing to one ellipsised line.
Along the way: a <td>'s own max-height doesn't cap table-row growth in any
browser (a table-layout quirk) -- a wrapped multi-line cell was silently
growing its row past rowH(r), which would have desynced every pixel
position computed from rowH()/rowTop() (the overlays, sticky frozen panes,
the row-resize handle). Fixed by moving cell text into its own block-level
.cell-content child, which *does* respect max-height + overflow:hidden.
Verified interactively against the dev server with Playwright: a long
formula gets a ~300px editor instead of the 104px cell; Alt+Enter produces
a real "\n" that survives commit and displays wrapped, with the row height
unchanged (26px, not the ~40px it grew to before the .cell-content fix);
F4 on a full-formula selection turns every reference absolute in one press
and keeps cycling all of them on repeated presses; existing selection,
copy/paste and fill-handle behaviour all regression-checked clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- store: fillSelection(tableId, source, target) repeats a source rectangle's
cells (values, formulas -- reference-adjusted via the same shiftFormula()
a copy uses -- and formatting) across a target rectangle. target may
extend the source in any single direction (up/down/left/right); a source
bigger than one row/column cycles through modulo its own size, so it
still fills correctly past a non-exact-multiple trailing repetition.
- TableGrid.vue: replaces the old decorative per-active-cell square with a
proper fill handle -- a small square overlaid at the bottom-right corner
of the *selection* (not just the anchor cell), draggable. Dragging shows
a live dashed preview of the cells about to be filled (snapping to
whichever axis -- vertical or horizontal -- is dragged furthest along,
same as Excel), and on mouseup commits via fillSelection() and grows the
selection to source + filled area, keeping the original anchor active.
Verified interactively against the dev server with Playwright: a formula
(=A1*2) filled down four rows lands as =A2*2/=A3*2/=A4*2 with correct
values, and a two-row source dragged right/down six cells cycles its
pattern correctly (1,2,1,2,1,2, including the trailing partial cycle).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copying a range showed the dashed marquee around the whole source, but the
distinct border on its first (anchor) cell disappeared the moment you
selected something else -- that border was drawn from the *live* selection's
anchor, which had just moved to the new (paste-destination) cell.
- ClipboardData now also stores the copy/cut selection's own anchor -- the
cell the drag/shift-selection actually started from, which isn't always
rect's top-left corner (a drag can start at any corner).
- clipboardRect exposes it; TableGrid renders a second small overlay,
.clip-anchor, positioned on just that one cell -- independent of the
current selection, so it stays visible for as long as the marquee does.
Verified interactively: copy B2:B5, click a cell elsewhere -- the marquee
and the solid border on B2 both remain, while the new cell gets its own
live-selection border.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Copy source marker: was outline:dashed on every td in the copied range,
so each cell got its own little dashed box. Replaced with one absolutely
positioned .clip-marquee overlay sized/positioned from the range's pixel
bounds (colLeft/rowTop), giving a single dashed rectangle around the whole
range, same as Excel/Numbers.
- Arrow keys (no Shift) after selecting a multi-cell range collapsed beside
the *focus* (wherever the drag/shift-selection ended), not the *anchor*
(the active, first-selected cell) -- inconsistent with the anchor-stays-
active fix from the previous change and with Excel/Numbers, where a plain
arrow key always steps from the active cell. navigate()/jumpTo()/Home/End
now step from the anchor when collapsing (ext=false) and keep stepping
from the focus when extending (Shift/Ctrl+Shift), which is unchanged.
Verified interactively against the dev server with Playwright (isolated
empty sheet, to sidestep the seed data's cascaded/overlapping tables):
selecting B2:B5 keeps the active-cell border on B2, copying shows exactly
one .clip-marquee, and ArrowLeft afterwards lands on A2, not A5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The engine already anticipated $A1/A$1/$A$1 in its grammar comment, but the
lexer had no rule for '$' at all -- any formula using it failed to parse.
- lexer.ts: '$' is now a valid word character, so "$A$1" tokenizes as one
IDENT, same as any other bare word (the lexer still doesn't interpret it,
per its "dumb lexer" design -- the parser/evaluator stay untouched, since
evaluation is purely positional and $ has no effect on a value).
- refs.ts: parseA1()/toA1() now carry colAbs/rowAbs through the $ markers
(backward compatible -- existing {col,row} destructuring is unaffected).
Added cycleReferenceLock() for Excel/Numbers-style F4: relative ->
absolute -> row-locked -> col-locked -> relative, toggling whichever
reference sits at the caret (and the correct side of an A1:B2 range).
- document.ts: shiftFormula() now honors the lock on copy (a locked axis
stays exactly as typed) but not on cut (a moved cell's reference follows
it regardless of $, since cut relocates the cell itself rather than
filling a relative offset).
- TableGrid.vue's inline cell editor and FormulaBar.vue both wire up F4 via
the shared cycleReferenceLock() helper.
- Tests: lexer/parser coverage for $ tokenizing/parsing, a new refs.spec.ts
for parseA1/toA1 round-tripping and the F4 cycle (incl. range side-
picking), and document.spec.ts cases for locked-axis copy vs. cut.
- README.md / src/engine/README.md: documented the $ semantics, F4, and
corrected a couple of now-stale claims (test count, "$ is ignored, there's
no copy/fill", "active cell is always the last-clicked one").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The active cell (bold border/handle, formula bar target) used to track
selection.focus, so it followed the mouse/cursor while dragging or
shift-extending a range. Switch it to selection.anchor -- the first cell of
the current selection action -- so it stays put while the range grows,
matching Excel/Numbers. Tab/Enter data-entry flow already keeps anchor ==
focus while it moves the cursor, so that behaviour is unaffected.
- document.ts: activeCell/activeCellName/activeCellInput read the anchor.
- FormulaBar.vue: commits to the anchor cell to match what it displays.
- TableGrid.vue: isActive() (border + handle) and F2/type-to-edit target
the anchor; Home/End keep using the cursor's row via s.focus.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- store: copySelection/cutSelection/pasteClipboard/cancelClipboard hold an
in-memory (unpersisted) clipboard buffer of raw cell inputs + formatting.
- shiftFormula rewrites every relative cell reference in a copied/cut formula
by the move delta (the engine has no $-absolute refs, so all refs qualify).
Copy shifts every reference uniformly; cut only follows references that
point inside the moved block itself, leaving refs to untouched cells alone
and never touching a qualified Table!A1 reference.
- paste anchors on a smaller/single-cell selection (and overflows past it),
or tiles across a selection that's an exact larger multiple of the buffer.
- TableGrid: Strg+C/X/V shortcuts, context-menu entries, a dashed "clip-src"
source marker, and Escape drops a pending buffer.
- document.spec.ts: coverage for reference shifting, tiled/fill paste, cut's
inside/outside reference split, format copy, and Escape cancelling.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>