diff --git a/lib/selection-manager.ts b/lib/selection-manager.ts index 14af597..0b21365 100644 --- a/lib/selection-manager.ts +++ b/lib/selection-manager.ts @@ -51,6 +51,7 @@ export class SelectionManager { // Store bound event handlers for cleanup private boundMouseUpHandler: ((e: MouseEvent) => void) | null = null; private boundContextMenuHandler: ((e: MouseEvent) => void) | null = null; + private boundClickHandler: ((e: MouseEvent) => void) | null = null; constructor( terminal: Terminal, @@ -291,6 +292,12 @@ export class SelectionManager { this.boundContextMenuHandler = null; } + // Clean up document click listener + if (this.boundClickHandler) { + document.removeEventListener('click', this.boundClickHandler); + this.boundClickHandler = null; + } + // Canvas event listeners will be cleaned up when canvas is removed from DOM } @@ -444,6 +451,21 @@ export class SelectionManager { }; canvas.addEventListener('contextmenu', this.boundContextMenuHandler); + + // Click outside canvas - clear selection + // This allows users to deselect by clicking anywhere outside the terminal + this.boundClickHandler = (e: MouseEvent) => { + // Check if the click is outside the canvas + const target = e.target as Node; + if (!canvas.contains(target)) { + // Clicked outside the canvas - clear selection + if (this.hasSelection()) { + this.clearSelection(); + } + } + }; + + document.addEventListener('click', this.boundClickHandler); } /** diff --git a/lib/terminal.test.ts b/lib/terminal.test.ts index a465ae3..0a44e97 100644 --- a/lib/terminal.test.ts +++ b/lib/terminal.test.ts @@ -666,6 +666,29 @@ describe('select()', () => { expect(fired).toBe(true); term.dispose(); }); + + test('should clear selection when clicking outside canvas', async () => { + const term = new Terminal({ cols: 80, rows: 24 }); + // Using shared container from beforeEach + if (!container) return; + await term.open(container); + + // Create a selection + term.select(0, 0, 10); + expect(term.hasSelection()).toBe(true); + + // Simulate click outside the canvas (on document body) + const clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window, + }); + document.body.dispatchEvent(clickEvent); + + // Selection should be cleared + expect(term.hasSelection()).toBe(false); + term.dispose(); + }); }); });