- 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>