('td button, td');
+ firstCell?.focus();
+ }
+ }
+ }, [selectedRows, pageData, selectAll, clearSelection, copySelectedAsTSV]);
+
+ // Global Ctrl+C listener for copying outside table focus
+ useEffect(() => {
+ const onGlobalCopy = (e: ClipboardEvent) => {
+ if (selectedRows.size > 0 && document.activeElement?.closest('[data-ledger-table]')) {
+ e.preventDefault();
+ copySelectedAsTSV();
+ }
+ };
+ document.addEventListener('copy', onGlobalCopy);
+ return () => document.removeEventListener('copy', onGlobalCopy);
+ }, [selectedRows, copySelectedAsTSV]);
+
// Page change doesn't reset expanded state — persistence across paging
// is intentional per the requirements.
@@ -167,6 +301,19 @@ export const Ledger: React.FC = () => {
Detailed transaction history and ledger entries.
+ {selectedRows.size > 0 && (
+
+
+ {selectedRows.size} selected
+
+ · Ctrl+C to copy as TSV
+ {copiedFeedback && (
+
+ Copied!
+
+ )}
+
+ )}
@@ -174,6 +321,9 @@ export const Ledger: React.FC = () => {
{/* Desktop table */}
{
{/* Parent row */}
{
+ const target = e.target as HTMLElement;
+ if (target.closest('button') || target.tagName === 'BUTTON') return;
+ toggleSelectRow(row.id, e as unknown as React.MouseEvent);
+ }}
aria-expanded={
hasSubEvents || hasEmptySubEvents
? isExpanded