# Numbers-Studie — Tabellenkalkulation mit eigener Formel-Engine Eine browserbasierte Tabellenkalkulation als **Case- und Designstudie** nach dem Vorbild von **Apple Numbers**. Zwei Dinge stehen im Mittelpunkt: 1. **Handwerk** — eine von Grund auf selbst geschriebene, schlanke Formel-Engine (Tokenizer, Parser, AST, Abhängigkeitsgraph, Neuberechnung, Zirkelbezug- Erkennung). Dokumentiert in [`src/engine/README.md`](src/engine/README.md). 2. **Produkt** — eine polierte, Numbers-nahe Oberfläche: jedes Blatt ist eine **freie Fläche (Canvas)**, auf die man beliebig viele **Tabellen- und Diagramm-Objekte** setzt, verschiebt, skaliert und befüllt. ```bash npm install npm run dev # Vite-Devserver npm run test # Vitest-Suite (Engine + Store + IO), 181 Tests npm run build # Typecheck + Produktionsbuild npm run lint # ESLint (flat config) ``` Alles läuft **rein clientseitig** — kein Server, keine Netzwerk-Requests. Datei- I/O über File API und Blob-URLs. --- ## Das Canvas-Modell Wie in Numbers ist ein *Blatt* kein einzelnes Endlos-Raster, sondern eine scroll- und zoombare Fläche, die **Objekte** trägt: - **Tabellen-Objekt** — ein eigenes Gitter mit dokumentweit eindeutigem Namen. Frei positionierbar (Ziehen an der Kopfleiste). Zwei Darstellungen: - **Automatisch** — so groß wie die Zellen, alle Zeilen sichtbar; der SE-Griff skaliert die Spaltenbreiten proportional. - **Feste Größe** — fester Viewport, **intern scrollbar**, mit frei wählbarer Zahl **fixierter Kopfzeilen und fixierter Spalten** (`position: sticky` gegen den Scroll-Container). Der SE-Griff zieht dann den Viewport auf. Spaltenbreite per Drag am Spaltenkopf (ab **24 px**), **Zeilenhöhe** per Drag an der Unterkante des Zeilenkopfs (Standard + je Zeile). - **Diagramm-Objekt** — Chart.js (Balken/Linie), frei positionier- **und** skalierbar. Datenquelle = ein Tabellen-Objekt; jede Zelländerung aktualisiert es sofort. - **Text-Objekt** — ein formatierbares Textfeld als Zusatz zu Tabellen und Diagrammen. Doppelklick zum Bearbeiten; Schriftgröße, Fett/Kursiv, Ausrichtung, Text- und Hintergrundfarbe im Inspector; frei skalierbar. Objekte kommen beim Anklicken in den Vordergrund, werden per `Entf` gelöscht (Diagramm/Text) bzw. über das `×` der Kopfleiste, per Doppelklick auf den Namen umbenannt. **Je Tabelle, Diagramm und Textfeld** sind eigene Hintergrund- und Textfarbe einstellbar. Mehrere Blätter über die Tabs unten (hinzufügen, umbenennen, löschen, per Drag sortieren). Zoom 40–200 % pro Blatt (`Strg/Cmd` + Mausrad, Zoom-Leiste oder Einstellungen). ### Zellauswahl - Klick = eine Zelle, **Shift+Klick / Ziehen** = ein Rechteck. Das Ziehen ist positions- statt event-target-basiert (`elementsFromPoint`), übersteht schnelle Bewegungen und das Verlassen der Tabelle, scrollt bei festen Tabellen an der Kante automatisch mit und heilt einen verlorenen `mouseup` selbst. - Kopf-Klick = ganze Spalte / Zeile, Ecke = alles. - **`Strg`/`Cmd`+Klick** = weitere Zelle **hinzufügen** (erneut = abwählen), **`Strg`/`Cmd`+Ziehen** = weiteres Rechteck; auch am Spalten-/Zeilenkopf. - Die **diskontinuierliche Mehrfachauswahl** trägt alle folgenden Aktionen: Live-Aggregat (Summe/Mittel/Anzahl) in der Statusleiste, die **Auswahl-Formatierung** im Inspector und `Entf` (leert alle markierten Zellen). - Pfeiltasten / Enter kollabieren wieder auf eine Zelle. Die **aktive Zelle** (blauer Rahmen, Formelleiste, `F2`/Tippen zum Bearbeiten) ist stets die **zuerst** angeklickte/gesetzte Zelle einer Auswahl — sie bleibt beim Ziehen/Erweitern eines Bereichs stehen, statt dem Cursor zu folgen (Excel-/ Numbers-Verhalten). Während einer Tab/Enter-Dateneingabefolge im Bereich wandert sie mit dem Cursor mit. - **Kopieren/Ausschneiden/Einfügen** (`Strg`/`Cmd`+`C`/`X`/`V`, Kontextmenü): überträgt Zellinhalte **und** Formatierung. Einfügen auf eine Auswahl, die ein exaktes Vielfaches der kopierten Größe ist, **kachelt**; eine kleinere oder Einzelzell-Auswahl verankert die kopierte Fläche dort und läuft darüber hinaus. Formeln werden dabei **bezugsangepasst** wie in Excel/Numbers — siehe „`$`-Bezüge sperren" unten; ein gestrichelter Rahmen markiert die Kopierquelle (mit eigenem Rahmen um deren erste Zelle, bleibt stehen, auch wenn die aktuelle Auswahl längst woanders ist), `Esc` verwirft den Puffer. - **Ausfüllkästchen** (das kleine Quadrat unten rechts an der Auswahl, wie in Excel/Numbers): ziehen wiederholt die Auswahl — Werte, Formeln (bezugsangepasst) und Formatierung — in die überstrichenen Zellen. Nur eine Achse gleichzeitig (rein runter/hoch/rechts/links, je nachdem wohin weiter gezogen wird); ein mehrzeiliger/-spaltiger Ausgangsbereich wird zyklisch wiederholt, auch über eine unvollständige letzte Wiederholung hinweg. **Tastaturnavigation (Excel-nah)** — Fokus in der Tabelle: | Taste | Verhalten | |---|---| | Pfeiltasten | eine Zelle; **Shift+Pfeil** erweitert die Auswahl | | **`Strg`/`Cmd`+Pfeil** | zum Rand des Datenblocks springen (leer↔gefüllt) | | **Tab / Shift+Tab** | rechts / links | | **Enter / Shift+Enter** | runter / hoch — nach einer Tab-Folge **zurück zur Startspalte + eine Zeile** | | bei aktivem **Bereich** | Tab/Enter **rotieren den Cursor im Bereich** (mit Umbruch), der Bereich bleibt markiert | | Tippen / `F2` | Zelle editieren; Enter/Tab committen und ziehen weiter (auch im Bereich) | | `Pos1` / `Strg`+`Pos1` / `Ende` / `Strg`+`Ende` | Zeilenanfang / A1 / Zeilenende / letzte Zelle | | `Bild↑` / `Bild↓` | seitenweise (bei festen Tabellen nach Viewport-Höhe) | | **`Strg`/`Cmd`+`C`/`X`/`V`** | Kopieren / Ausschneiden / Einfügen (Zellen + Formatierung, bezugsangepasst) | | **`Strg`/`Cmd`+`Z`** · **`Strg`/`Cmd`+`Y`** / **`Strg`/`Cmd`+`Shift`+`Z`** | Rückgängig · Wiederholen (auch als Symbolleisten-Buttons ↶ ↷) | | **`F4`** (im Editor/in der Formelleiste) | Bezug am Cursor sperren/zyklisch umschalten: `A1` → `$A$1` → `A$1` → `$A1` → `A1`; bei markiertem Text werden **alle** darin gefundenen Bezüge auf einmal umgeschaltet, jeder ausgehend vom eigenen Zustand | | **`Alt`+`Enter`** bzw. **`Cmd`+`Enter`** (im Editor) | Zeilenumbruch in der Zelle einfügen, statt zu committen — `Cmd`+`Enter` als Mac-Fallback, da `⌥` (Option) dort eine Compose-Taste ist und nicht überall zuverlässig als reines Modifier ankommt | Bei **festen (scrollbaren) Tabellen** wird die aktive Zelle bei jeder Navigation automatisch in den sichtbaren Bereich gescrollt (unter Beachtung fixierter Zeilen/Spalten). **Rückgängig/Wiederholen**: dokumentweiter Undo-Stack (Snapshot-basiert, wie der bereits vorhandene JSON-Export/Import-Round-Trip — einfach und nachweisbar korrekt für ein Dokument dieser Größe, statt eines Patch-/ Kommando-Logs). Reine Auswahl-/Cursor-Bewegungen zählen nie als Schritt; eine schnelle Folge von Mutationen (ein Spalten-/Zeilen-Resize-Drag feuert bei jeder Mausbewegung) wird zu **einem** Schritt zusammengefasst, sofern sie innerhalb von 150 ms aufeinanderfolgen — knapp unter normalem Tipp-/Klick- Tempo, damit zwei tatsächlich getrennte schnelle Aktionen nicht verschmelzen, aber weit über dem Abstand aufeinanderfolgender Drag-Ereignisse (~16–30 ms). Ein neuer Bearbeitungsschritt verwirft sofort einen etwaigen Wiederholen-Ast. „Auf Beispiel zurücksetzen" und JSON-Import „Ersetzen" starten eine **neue** Historie (das vorherige Dokument ist danach nicht mehr zurückholbar), Undo/ Redo selbst tun das nicht. Symbolleiste zeigt zwei Buttons (↶ ↷, deaktiviert wenn nichts zu tun ist); greift auch ohne Tabellenfokus, außer während eine Texteingabe (Zell-Editor, Formelleiste, Blatt-Umbenennen, …) den nativen Undo dieses Feldes benutzen soll. **In-Zellen-Editor**: das Eingabefeld liegt als Overlay über der Zelle (nicht in ihr) und wächst mit dem Inhalt — in der Breite bis zu einer angenäherten „sichtbaren Anzeigebreite", danach umbrechend; in der Höhe mit `Alt`+`Enter` eingefügten Zeilenumbrüchen, bis zu einem Maximum, danach intern scrollbar. Ein Zeilenumbruch bleibt auch nach dem Committen erhalten und wird in der Zelle als solcher dargestellt (umbrechend statt einzeiliger Ellipse), auf die Zeilenhöhe der Tabelle geklemmt. **Formelleiste** (`fx`, oben): ein `