Keep marking the copy source's first cell after the selection moves on

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>
This commit is contained in:
wkh
2026-09-02 10:35:35 +02:00
co-authored by Claude Sonnet 5
parent 47afe8fc20
commit fafb118db6
3 changed files with 56 additions and 2 deletions
+23
View File
@@ -137,6 +137,20 @@ const clipMarqueeStyle = computed(() => {
height: rowTop(k.r1 + 1) - top + 'px',
};
});
/** The copy/cut selection's *own* first cell, called out inside the marquee
* even once the live selection has moved on to pick a paste destination
* (which is when `is-active` no longer sits there). */
const clipAnchorStyle = computed(() => {
const k = clipRect.value;
if (!k || k.tableId !== props.tableId) return null;
const { col, row } = k.anchor;
return {
left: colLeft(col) + 'px',
top: rowTop(row) + 'px',
width: colWidth(col) + 'px',
height: rowH(row) + 'px',
};
});
const clampC = (c: number) => Math.max(0, Math.min((table.value?.cols ?? 1) - 1, c));
const clampR = (r: number) => Math.max(0, Math.min((table.value?.rows ?? 1) - 1, r));
@@ -771,6 +785,7 @@ defineExpose({
</tbody>
</table>
<div v-if="clipMarqueeStyle" class="clip-marquee" :style="clipMarqueeStyle" />
<div v-if="clipAnchorStyle" class="clip-anchor" :style="clipAnchorStyle" />
</div>
<ContextMenu v-model:open="menu.open" :x="menu.x" :y="menu.y" :items="menuItems" />
@@ -909,6 +924,14 @@ defineExpose({
opacity: 0.45;
}
}
/* the copy/cut selection's own first cell -- called out inside the marquee
even once the live selection has moved elsewhere to pick a paste target */
.clip-anchor {
position: absolute;
pointer-events: none;
box-shadow: inset 0 0 0 2px var(--accent);
z-index: 8;
}
.handle {
position: absolute;
right: -3px;