Commit Graph
8 Commits
Author SHA1 Message Date
wkhandClaude Sonnet 5 5a06d08a6a Implement document-wide undo/redo
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>
2026-09-02 14:19:43 +02:00
wkhandClaude Sonnet 5 67b011637d Make the formula bar mouse-resizable and wrapping
Switches the formula bar from a single-line <input> to a <textarea>:

- A plain <textarea> gets a free, native mouse-draggable resize handle in
  its bottom-right corner (CSS resize: vertical) -- exactly the "erweiterbar/
  erhöhbar mit der Maus" ask, no custom drag code needed.
- Unlike the old <input>, it actually wraps a long formula instead of just
  scrolling it sideways, so dragging the handle taller has something to
  show (min-height keeps the collapsed case looking like before; max-height
  caps it at half the viewport).
- Brought its keyboard handling in line with the in-cell editor from the
  last two changes: Alt+Enter/Cmd+Enter insert a literal newline, F4 cycles
  every reference a selection overlaps, Escape reverts and blurs.
- .formula-bar switches to align-items: flex-start (with matching top
  margins on the labels) so the cell-name/fx labels stay pinned to the
  first line instead of vertically centering in a now-tall box.

Verified interactively: dragging the handle down 80px grows the bar from
~34px to ~113px; Alt+Enter inserts a real newline in the (now multi-line-
capable) field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 11:11:01 +02:00
wkhandClaude Sonnet 5 8c9f3bbc87 Fix Alt+Enter newline not registering on Mac: add Cmd+Enter fallback
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>
2026-09-02 11:08:21 +02:00
wkhandClaude Sonnet 5 cc76ea547f Auto-sizing cell editor, multi-ref F4, and Alt+Enter line breaks
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>
2026-09-02 11:04:45 +02:00
wkhandClaude Sonnet 5 830dcb8eeb Add the fill handle: drag the selection's corner to copy/fill, Excel-style
- 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>
2026-09-02 10:43:54 +02:00
wkhandClaude Sonnet 5 8e43cc294c Support $-locked references: relative, mixed and absolute
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>
2026-09-02 10:11:10 +02:00
wkh 31108977b9 - before copy paste 2026-09-02 09:45:44 +02:00
wkh c7885067e7 - init 2026-08-29 17:47:12 +02:00