- init
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
# 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), 102 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
|
||||
(Formelleiste) ist stets die zuletzt angeklickte.
|
||||
|
||||
### Zell-Formatierung
|
||||
|
||||
Auswahl (Zelle, Bereich, Mehrfachauswahl, ganze Zeile/Spalte über den Kopf) → im
|
||||
Inspector unter **Objekt · Auswahl**: horizontale **Ausrichtung**
|
||||
(links/mitte/rechts),
|
||||
**Zahlenformat** (`Automatisch`, `Ohne`, Ganzzahl, 1/2 Nachkommastellen,
|
||||
**Prozent** 0/2 Stellen — kombiniert mit der Locale unten), **Zell-Textfarbe**,
|
||||
**Zell-Füllfarbe**, **Fett**/**Kursiv**, „Formatierung entfernen". Auch exakte
|
||||
**Spaltenbreite** und **Zeilenhöhe** der Fokuszelle.
|
||||
|
||||
### Einstellungen (⚙, links neben dem Titel)
|
||||
|
||||
Ein Zahnrad neben der Überschrift öffnet eine linke Inspector-Schublade
|
||||
([`SettingsPanel.vue`](src/components/SettingsPanel.vue)), die die Fläche zur
|
||||
Seite schiebt (auf schmalen Viewports überlagert sie). Sie bündelt alle
|
||||
wichtigen Punkte:
|
||||
|
||||
- **Objekt** (das gewählte):
|
||||
- Tabelle → Name, Zeilen/Spalten (exakt), Darstellung, Viewport-Größe,
|
||||
fixierte Kopfzeilen/Spalten, Position, Zahlenformat-Locale, **Standard-
|
||||
Zeilenhöhe**, **Tabellen-Hintergrund/-Textfarbe**, und – bei aktiver
|
||||
Zellauswahl – die **Auswahl**-Formatierung (s. o.).
|
||||
- Diagramm → Name, Ansicht, Quelltabelle, Größe, Position, Zahlenformat-Locale,
|
||||
**Hintergrund**, **Text-/Achsenfarbe**.
|
||||
- Textfeld → Name, Text, Schriftgröße, Fett/Kursiv, Ausrichtung, **Textfarbe**,
|
||||
**Hintergrund**, Größe, Position.
|
||||
- **Blatt**: Name, Zahlenformat, **Hintergrundfarbe**, **Schriftfarbe**,
|
||||
**Raster ein-/ausblenden** (`↺` setzt eine Farbe auf den Theme-Wert zurück),
|
||||
neues Blatt, Blatt löschen.
|
||||
- **Ansicht**: Zoom-Regler (**pro Blatt** gespeichert).
|
||||
- **Dokument**: Erscheinungsbild (Auto/Hell/Dunkel), Zahlenformat
|
||||
(Ländereinstellung), CSV-Standard-Trennzeichen, „Auf Beispiel zurücksetzen".
|
||||
|
||||
### Blatt-Erscheinungsbild
|
||||
|
||||
Jedes Blatt kann eine eigene **Hintergrundfarbe** (Canvas-Fläche hinter den
|
||||
Objekten) und **Schriftfarbe** (Standard-Textfarbe der Gitter/Labels) haben und
|
||||
das **Punkt-Raster** ein- oder ausblenden. Die Farben überschreiben die
|
||||
Theme-Variablen `--bg` / `--text` nur für dieses Blatt; ohne Wert gilt das
|
||||
Theme. Der Canvas-**Zoom** wird ebenfalls pro Blatt gemerkt.
|
||||
|
||||
Alle diese Einstellungen sind Teil des Dokuments (`DocumentState` /
|
||||
`Sheet`) und werden damit in **localStorage gespeichert und in `.json`
|
||||
exportiert** – Round-Trip verlustfrei, ungültige Werte werden beim Import
|
||||
verworfen bzw. geklemmt.
|
||||
|
||||
### Zahlenformat (Ländereinstellung)
|
||||
|
||||
Die Darstellung von Zahlen (Dezimal- und Tausendertrennzeichen) ist eine
|
||||
**Kaskade**: Objekt → Blatt → Dokument. Jede Ebene kann die darüberliegende
|
||||
überschreiben oder „erben" (leere Auswahl). Auswahl: `Ohne Trennzeichen`,
|
||||
`de-DE`, `de-CH`, `en-US`, `en-GB`, `fr-FR`, `it-IT`. Wirkt auf Zellwerte, das
|
||||
Live-Aggregat der Statusleiste und die Wertelabels der Diagramme. **CSV-/XLSX-
|
||||
Export bleibt bewusst locale-neutral** (`.`-Dezimal, keine Gruppierung) für einen
|
||||
verlustfreien Round-Trip; der CSV-*Import* erkennt beide Schreibweisen ohnehin.
|
||||
|
||||
Das vorbefüllte „Umsatz" ist bereits als **feste, scrollbare Tabelle mit
|
||||
fixierter Kopfzeile und fixierter Spalte A** angelegt, um das Feature zu zeigen.
|
||||
|
||||
---
|
||||
|
||||
## Formelgrammatik (EBNF)
|
||||
|
||||
```ebnf
|
||||
formula = "=" expression ;
|
||||
|
||||
expression = term { ("+" | "-") term } ; (* links-assoziativ *)
|
||||
term = unary { ("*" | "/") unary } ; (* links-assoziativ *)
|
||||
unary = ("+" | "-") unary
|
||||
| power ;
|
||||
power = primary [ "^" unary ] ; (* rechts-assoziativ; bindet
|
||||
stärker als unäres Minus,
|
||||
d.h. -2^2 = -(2^2) = -4 *)
|
||||
primary = NUMBER | STRING | "(" expression ")" | funcall | reference ;
|
||||
|
||||
funcall = IDENT "(" [ expression { ";" expression } ] ")" ;
|
||||
|
||||
reference = [ qualifier ] CELL [ ":" CELL ] ;
|
||||
qualifier = (IDENT | QUOTED) ("!" | "::") ; (* Tabellenname *)
|
||||
|
||||
IDENT = /[A-Za-z_][A-Za-z0-9_]*/ ;
|
||||
QUOTED = /' ( [^'] | '' )* '/ ; (* Name mit Leerzeichen *)
|
||||
CELL = /\$?[A-Za-z]{1,3}\$?[0-9]{1,7}/ ;
|
||||
```
|
||||
|
||||
Präzedenz schwach → stark: `+ -` < `* /` < unäres `+ -` < `^`.
|
||||
|
||||
Der Qualifier ist der **Tabellenname** (nicht der Blattname). `!` und der
|
||||
Numbers-Stil `::` sind austauschbar. Tabellennamen werden dokumentweit eindeutig
|
||||
gehalten und **case-/whitespace-tolerant** aufgelöst:
|
||||
`Kosten!A2` = `'Kosten'!A2` = `kosten!a2` = `Kosten::A2`.
|
||||
|
||||
**Funktionen** (case-insensitiv, Trenner `;`): `SUM`, `AVG` (Alias `AVERAGE`),
|
||||
`MIN`, `MAX`, `COUNT`. Argumente gemischt: Einzelzellen, Bereiche (lokal oder
|
||||
`Name!…`), Zahl-Literale — z. B. `=SUM(Umsatz!B2:E2; 100; A1)`.
|
||||
|
||||
**Fehlermodell** (propagiert an abhängige Zellen):
|
||||
`#REF` (ungültiger/gelöschter Bezug oder unbekannter Tabellenname), `#DIV/0!`,
|
||||
`#NAME?` (unbekannte Funktion), `#ERR` (Parse-Fehler), `#CIRC` (Zirkelbezug,
|
||||
auch tabellenübergreifend), `#VALUE!` (Typfehler in Arithmetik — *dokumentierte
|
||||
Erweiterung*). Referenz auf leere Zelle = `0` in Arithmetik, von `COUNT` nicht
|
||||
gezählt.
|
||||
|
||||
---
|
||||
|
||||
## Architektur der Engine-Module
|
||||
|
||||
Framework-agnostisches ES-Modul unter [`src/engine/`](src/engine/) — **keine**
|
||||
Formel-/Spreadsheet-Library. Kennt kein Vue, kein DOM. Die adressierbare Einheit
|
||||
ist eine **Tabelle**; der Abhängigkeitsgraph läuft dokumentweit über alle
|
||||
Tabellen aller Blätter.
|
||||
|
||||
| Datei | Aufgabe |
|
||||
|-------|---------|
|
||||
| `lexer.ts` | Tokenizer, flacher Token-Strom (inkl. `!` und `::`). |
|
||||
| `parser.ts` | Recursive-Descent → AST; EBNF im Dateikopf; `collectRefs()`. |
|
||||
| `evaluator.ts` | AST + `EngineHost` → Wert; Fehler-Propagierung. |
|
||||
| `graph.ts` | `DependencyGraph`: `affected()`, `topoOrder()`, Zirkel-DFS. |
|
||||
| `refs.ts` | A1 ↔ `(col,row)`, Bereichs-Expansion. |
|
||||
| `functions.ts` | `{ NAME → fn(values) }` für SUM/AVG/MIN/MAX/COUNT. |
|
||||
| `workbook.ts` | Flache Tabellenliste + ASTs + Werte + Graph; inkrementelle Neuberechnung; implementiert `EngineHost`. |
|
||||
|
||||
**Neuberechnung.** `setCell` reparst eine Zelle → `graph.affected(dirty)` →
|
||||
`graph.topoOrder()` → Zyklus-Zellen `#CIRC`, Rest topologisch auswerten, nicht
|
||||
betroffene Zellen behalten ihren Wert.
|
||||
|
||||
**Anbindung an Vue.** [`src/store/document.ts`](src/store/document.ts) ist das
|
||||
„eigene `reactive()`-Modul": es hält eine `Workbook`-Instanz und einen
|
||||
`recalcTick`-Ref. Die UI liest `store.cellValue(...)`, das `recalcTick` berührt —
|
||||
nach jeder Neuberechnung wird der Tick erhöht, jede Formelzelle re-rendert also
|
||||
**wie ein `computed`**.
|
||||
|
||||
---
|
||||
|
||||
## Selbst gebaut vs. Library
|
||||
|
||||
| Selbst gebaut | Library |
|
||||
|---------------|---------|
|
||||
| **Formel-Engine komplett** (Lexer, Parser, AST, Evaluator, dokumentweiter Abhängigkeitsgraph, Topo-Sort, Zirkel-Erkennung, Funktionen) | **Vue 3** + Vite |
|
||||
| **Die Canvas**: Objekte platzieren, verschieben, skalieren, Z-Order, Zoom | **Chart.js** (Balken + Linie) |
|
||||
| **Das Raster** je Tabelle: Auswahl (Klick/Shift/Drag), Tastaturnavigation, In-Zellen-Edit, Spaltenbreite per Drag | **xlsx (SheetJS)** — nur `.xlsx` I/O, lazy geladen, nie zur Auswertung |
|
||||
| Reaktiver **Store** / Dokumentmodell (v1 → v2 Migration) | — |
|
||||
| **CSV-Parser + -Writer** (RFC 4180, Quoting, Delimiter-Sniffing, Locale-Zahlen) | |
|
||||
| **JSON**-Serialisierung + Schema-Validierung | |
|
||||
| Sheet-Tabs (Drag-Sortierung), Kontextmenü, Modals, Toasts, Theme | |
|
||||
|
||||
Bewusst **nicht** verwendet: Handsontable, AG Grid, Univer, Luckysheet,
|
||||
HyperFormula, formula-parser. `@vueuse/core`/`splitpanes` (in der Aufgabe als
|
||||
optionale Komfort-Libs genannt) wurden nach dem Umbau auf das Canvas-Modell nicht
|
||||
mehr gebraucht und wieder entfernt, um den Abhängigkeitsbaum ehrlich zu halten.
|
||||
|
||||
---
|
||||
|
||||
## Import / Export
|
||||
|
||||
Alles clientseitig (`Blob` + `URL.createObjectURL` + `<a download>` + revoke).
|
||||
|
||||
### Export
|
||||
| Format | Inhalt |
|
||||
|--------|--------|
|
||||
| `.json` | **Natives Format**, verlustfreier Round-Trip: alle Blätter, alle Tabellen-/Diagramm-Objekte mit Position & Größe, Rohformeln, Spaltenbreiten, Tab-Reihenfolge, aktives Objekt, Zellauswahl. |
|
||||
| `.csv` | Aktive Tabelle, **berechnete Werte**, RFC-4180-Quoting, Trennzeichen `,`/`;` wählbar, `\r\n`, optional UTF-8-BOM. |
|
||||
| `.xlsx` | Arbeitsmappe via SheetJS, **ein Worksheet je Tabellen-Objekt**. Formeln als echte Excel-Formeln (`;`→`,`, `AVG`→`AVERAGE`), immer mit gecachtem Wert. |
|
||||
| `.png` | Gewähltes Diagramm-Objekt über `chart.toBase64Image()`. |
|
||||
|
||||
### Import
|
||||
`<input type="file">` **und** Drag & Drop auf die Canvas; `.json` `.csv` `.xlsx`.
|
||||
|
||||
- **`.json`** → Dokument *ersetzen* (Rückfrage) oder Blätter *anhängen*.
|
||||
**v1-Dokumente** (altes Ein-Raster-Format) werden automatisch nach v2
|
||||
migriert (je altes Blatt ein Blatt mit einer gleichnamigen Tabelle). Schema
|
||||
wird validiert; bei Fehler klare Meldung, **kein Teil-Import**.
|
||||
- **`.csv`** → „als neue Tabelle (aktuelles Blatt)" / „aktive Tabelle ab A1
|
||||
ersetzen". Trennzeichen-Sniffing (+ Override). Locale-Erkennung
|
||||
(`1.234,56` ↔ `1,234.56`). Werte mit führendem `=` werden zu Formeln.
|
||||
- **`.xlsx`** → je Worksheet ein neues Blatt mit einer Tabelle.
|
||||
- Nach jedem Import: kompletter Reparse + Neuberechnung + Diagramm-Refresh.
|
||||
Schlägt ein Import fehl, bleibt das bestehende Dokument **unverändert**.
|
||||
|
||||
## Persistenz
|
||||
|
||||
Das gesamte Dokument wird (debounced) nach `localStorage` gespiegelt, robust
|
||||
gegen leeren/kaputten State — Fallback ist das Seed-Dokument. Ein v1-State im
|
||||
Storage wird beim Laden migriert. Button **„Zurücksetzen"** stellt das
|
||||
Seed-Dokument wieder her.
|
||||
|
||||
## Vorbefülltes Dokument
|
||||
|
||||
Blatt **„Analyse"** mit drei Tabellen-Objekten (**Umsatz**, **Kosten**,
|
||||
**Übersicht**) und einem **Diagramm** (Umsatz je Region), plus ein leeres Blatt
|
||||
**„Blatt 2"**. „Übersicht" demonstriert tabellenübergreifende Bezüge und die vier
|
||||
Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
|
||||
`E2 = =D2/B2*100`. Siehe [`src/data/seed.ts`](src/data/seed.ts).
|
||||
|
||||
> **Hinweis zur Aufgabenstellung:** Die Akzeptanzliste nennt „Übersicht!B2 =
|
||||
> 6070". Die explizit angegebene Nord-Zeile ergibt jedoch
|
||||
> `1200+1450+1600+1720 = 5970`. Die Studie hält sich an die explizite Tabelle;
|
||||
> das demonstrierte Verhalten (Sofort-Propagierung durch Übersicht, „Gesamt" und
|
||||
> das Diagramm bei Änderung von `Umsatz!B2`) ist davon unberührt.
|
||||
|
||||
## Tests
|
||||
|
||||
`npm run test` — Vitest, 102 Tests:
|
||||
|
||||
- **lexer / parser** — Präzedenz & Assoziativität (`^` vor `*/` vor `+-`, `^`
|
||||
rechts, `-2^2 = -(2^2)`), lokale/qualifizierte/quoted Bezüge, `!` **und** `::`,
|
||||
Bereiche, `;`-Argumente, Parse-Fehler.
|
||||
- **evaluator** — Arithmetik, leere Zelle = 0, alle Funktionen inkl. Alias,
|
||||
Cross-Table, Case-/`::`-Toleranz, jeder Fehlerfall, Fehler-Propagierung.
|
||||
- **graph** — `affected`, topologische Ordnung, Selbst-/2er-/längerer Zyklus.
|
||||
- **workbook / store** — Seed-Aggregate, Cross-Table-Summen, Edit-Ripple durch
|
||||
die ganze Kette, `#REF` nach Tabellen-/Blatt-Löschung, neues Tabellen-Objekt
|
||||
mit Cross-Sheet-Formel, `resetToSeed`, Live-Auswahl-Aggregat,
|
||||
Neuberechnungs-**Reihenfolge**, **v1 → v2 Migration**.
|
||||
- **Tabellen-Ansicht** — Seed-Umsatz ist fixiert/scrollbar; `setTableView`
|
||||
klemmt fixierte Zeilen/Spalten an die Gittergröße; Verkleinern löscht
|
||||
Zellen außerhalb und klemmt nach; Ansichts-Einstellungen im JSON-Round-Trip.
|
||||
- **Zahlenformat-Kaskade** — `cellDisplay` nutzt Dokument-Locale; Blatt-Override
|
||||
schlägt Dokument, Objekt-Override schlägt Blatt; Zurücksetzen fällt die
|
||||
Kaskade hinab; Overrides im JSON-Round-Trip, ungültige Werte werden verworfen.
|
||||
- **Blatt-Erscheinungsbild** — Hintergrund/Schrift/Raster sind pro Blatt und
|
||||
löschbar; Zoom pro Blatt (folgt dem Tab); Erscheinungsbild + Zoom +
|
||||
CSV-Default im JSON-Round-Trip, kaputte Farb-/Zoom-Werte verworfen bzw.
|
||||
geklemmt.
|
||||
- **Zell-Formatierung** — per-Zelle Zahlenformat × Locale (inkl. Prozent);
|
||||
`setRangeFormat` über einen Bereich, Löschen per Falsy/`auto`; `selectionFormat`
|
||||
meldet nur gemeinsame Werte; Zeilenhöhe (Standard + je Zeile), Spaltenbreite ab
|
||||
24 px; Verkleinern pruned `formats`/`rowHeights`; alles im JSON-Round-Trip.
|
||||
- **Text-Objekte** — einfügen/bearbeiten/formatieren + Round-Trip; kaputtes
|
||||
Text-Objekt im Import wird abgelehnt, Dokument bleibt unverändert.
|
||||
- **Objekt-Farben** — je Tabelle/Diagramm/Textfeld eigenes `bg`/`fg`, löschbar,
|
||||
im JSON-Round-Trip.
|
||||
- **Mehrfachauswahl** — `addToSelection` behält frühere Rechtecke & verschiebt
|
||||
die aktive Zelle, erneutes Klicken schaltet eine 1×1-Auswahl ab,
|
||||
`extendActiveRange` wächst das jüngste Rechteck; Aggregat & Formatierung über
|
||||
**alle** Rechtecke; `#`-genaue Zellzahl; JSON-Round-Trip inkl. `ranges`,
|
||||
Klemmung beim Verkleinern.
|
||||
- **IO** — JSON verlustfreier Round-Trip (identisch), defekte/leere Datei →
|
||||
Fehler ohne Nebenwirkung, unbekannter Tabellenname → `#REF`, v1→v2 beim
|
||||
Import; CSV Übersicht-Export→Re-Import gleiche Zahlen, RFC-4180-Quoting,
|
||||
`=1+2`→3, Locale-Zahlen.
|
||||
|
||||
---
|
||||
|
||||
## Bekannte Grenzen
|
||||
|
||||
- **Zeilen/Spalten nur am Rand.** Einfügen/Löschen *innerhalb* eines Gitters (mit
|
||||
automatischem Umschreiben relativer Bezüge) ist bewusst nicht implementiert.
|
||||
- **Objekt-Bezüge beim Anhängen.** Beim JSON-*Anhängen* zweier Dokumente werden
|
||||
nur Blatt-/Objekt-IDs neu vergeben, nicht Tabellennamen. Kollidiert ein
|
||||
angehängter Tabellenname mit einem bestehenden, gewinnt bei der Auflösung der
|
||||
erste Treffer. „Ersetzen" (der Round-Trip-Fall) ist davon nicht betroffen.
|
||||
- **Kein Virtual Scrolling** — „Feste Größe" scrollt intern, aber alle Zeilen
|
||||
bleiben im DOM (nur geclippt); sehr große Tabellen/Importe (viele Tausend
|
||||
Zeilen) werden träge.
|
||||
- **Große Dateien** werden per `setTimeout`-Yield verarbeitet (UI bleibt
|
||||
reaktiv), **kein** echter Web Worker.
|
||||
- **xlsx-Formel-Übernahme** ist heuristisch (Separator-Swap unter Beachtung von
|
||||
Quotes); exotische Excel-Formeln fallen auf den Wert zurück.
|
||||
- Kein Text-Verkettungs-/Vergleichsoperator, keine benannten Bereiche, kein
|
||||
Kopieren/Einfügen von Zellbereichen, keine Undo-Historie, kein Snapping der
|
||||
Canvas-Objekte.
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```
|
||||
src/
|
||||
engine/ # Formel-Engine (Eigenbau) + Unit-Tests
|
||||
store/ # reaktiver Dokument-Store (v2 Canvas-Modell), Formatierung
|
||||
io/ # csv / json (Eigenbau, inkl. v1→v2), xlsx (SheetJS, lazy)
|
||||
components/ # AppToolbar, SettingsPanel, FormulaBar, SheetCanvas, CanvasObject,
|
||||
# TableGrid, ChartView, TextBox, SheetTabs, StatusBar, …
|
||||
composables/ # Theme, Toasts
|
||||
data/seed.ts # vorbefülltes Dokument
|
||||
styles/main.css # Design-Tokens (Light/Dark)
|
||||
```
|
||||
Reference in New Issue
Block a user