Keep the first-selected cell marked as active during a multi-cell selection
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>
This commit is contained in:
@@ -17,7 +17,7 @@ watch(
|
|||||||
|
|
||||||
function commit() {
|
function commit() {
|
||||||
const sel = store.doc.selection;
|
const sel = store.doc.selection;
|
||||||
if (sel) store.setCellInput(sel.tableId, sel.focus.col, sel.focus.row, draft.value);
|
if (sel) store.setCellInput(sel.tableId, sel.anchor.col, sel.anchor.row, draft.value);
|
||||||
}
|
}
|
||||||
function onEnter() {
|
function onEnter() {
|
||||||
commit();
|
commit();
|
||||||
|
|||||||
@@ -111,8 +111,12 @@ const mine = computed(
|
|||||||
);
|
);
|
||||||
/** All selected rectangles (primary + any additional) for this table. */
|
/** All selected rectangles (primary + any additional) for this table. */
|
||||||
const selRects = computed(() => (mine.value ? store.selectionRects(mine.value) : []));
|
const selRects = computed(() => (mine.value ? store.selectionRects(mine.value) : []));
|
||||||
|
/** The active cell is the anchor -- the first cell of the current selection --
|
||||||
|
* so it stays marked while a multi-cell selection is dragged/extended out
|
||||||
|
* from it (Excel/Numbers behaviour). During Tab/Enter data-entry flow the
|
||||||
|
* anchor is kept equal to the flowing cell, so this still tracks it there. */
|
||||||
const isActive = (c: number, r: number) =>
|
const isActive = (c: number, r: number) =>
|
||||||
!!mine.value && mine.value.focus.col === c && mine.value.focus.row === r;
|
!!mine.value && mine.value.anchor.col === c && mine.value.anchor.row === r;
|
||||||
const inSelection = (c: number, r: number) =>
|
const inSelection = (c: number, r: number) =>
|
||||||
selRects.value.some((k) => c >= k.c0 && c <= k.c1 && r >= k.r0 && r <= k.r1);
|
selRects.value.some((k) => c >= k.c0 && c <= k.c1 && r >= k.r0 && r <= k.r1);
|
||||||
const colHot = (c: number) => selRects.value.some((k) => c >= k.c0 && c <= k.c1);
|
const colHot = (c: number) => selRects.value.some((k) => c >= k.c0 && c <= k.c1);
|
||||||
@@ -481,7 +485,7 @@ function onKeydown(e: KeyboardEvent) {
|
|||||||
const s = mine.value;
|
const s = mine.value;
|
||||||
const t = table.value;
|
const t = table.value;
|
||||||
if (!s || !t) return;
|
if (!s || !t) return;
|
||||||
const { col, row } = s.focus;
|
const { row } = s.focus; // Home/End jump within the cursor's current row
|
||||||
const ext = e.shiftKey;
|
const ext = e.shiftKey;
|
||||||
const mod = e.ctrlKey || e.metaKey;
|
const mod = e.ctrlKey || e.metaKey;
|
||||||
|
|
||||||
@@ -532,7 +536,7 @@ function onKeydown(e: KeyboardEvent) {
|
|||||||
case 'F2':
|
case 'F2':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return beginEdit(col, row);
|
return beginEdit(s.anchor.col, s.anchor.row);
|
||||||
case 'Home':
|
case 'Home':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -566,7 +570,7 @@ function onKeydown(e: KeyboardEvent) {
|
|||||||
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
beginEdit(col, row, e.key);
|
beginEdit(s.anchor.col, s.anchor.row, e.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,9 @@ describe('seed document acceptance (canvas model)', () => {
|
|||||||
s.setSelection({ tableId: umsatz, anchor: { col: 1, row: 1 }, focus: { col: 1, row: 5 } });
|
s.setSelection({ tableId: umsatz, anchor: { col: 1, row: 1 }, focus: { col: 1, row: 5 } });
|
||||||
expect(s.selectionAggregate.value.sum).toBe(6175);
|
expect(s.selectionAggregate.value.sum).toBe(6175);
|
||||||
expect(s.selectionAggregate.value.count).toBe(5);
|
expect(s.selectionAggregate.value.count).toBe(5);
|
||||||
expect(s.activeCellName.value).toBe(`Umsatz::${toA1(1, 5)}`);
|
// the active cell is the anchor -- the first cell of the selection -- not
|
||||||
|
// wherever the selection was extended to.
|
||||||
|
expect(s.activeCellName.value).toBe(`Umsatz::${toA1(1, 1)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('recalculation visits precedents before dependents across tables', () => {
|
it('recalculation visits precedents before dependents across tables', () => {
|
||||||
|
|||||||
@@ -993,18 +993,21 @@ function createStore() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeCell = computed<CellCoord>(() => doc.selection?.focus ?? { col: 0, row: 0 });
|
// The "active cell" is always the anchor -- the first cell of the current
|
||||||
|
// selection action (click, shift-click, drag start, ...) -- so it stays put
|
||||||
|
// while a multi-cell selection is being made, matching Excel/Numbers.
|
||||||
|
const activeCell = computed<CellCoord>(() => doc.selection?.anchor ?? { col: 0, row: 0 });
|
||||||
|
|
||||||
const activeCellName = computed(() => {
|
const activeCellName = computed(() => {
|
||||||
const sel = doc.selection;
|
const sel = doc.selection;
|
||||||
const t = sel ? tableById(sel.tableId) : activeTable.value;
|
const t = sel ? tableById(sel.tableId) : activeTable.value;
|
||||||
const cell = sel?.focus ?? { col: 0, row: 0 };
|
const cell = sel?.anchor ?? { col: 0, row: 0 };
|
||||||
return `${t?.name ?? '—'}::${toA1(cell.col, cell.row)}`;
|
return `${t?.name ?? '—'}::${toA1(cell.col, cell.row)}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeCellInput = computed(() => {
|
const activeCellInput = computed(() => {
|
||||||
const sel = doc.selection;
|
const sel = doc.selection;
|
||||||
return sel ? cellInput(sel.tableId, sel.focus.col, sel.focus.row) : '';
|
return sel ? cellInput(sel.tableId, sel.anchor.col, sel.anchor.row) : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectionAggregate = computed(() => {
|
const selectionAggregate = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user