From 0291fe46f09b78402b4b06047443741bcc7c8776 Mon Sep 17 00:00:00 2001 From: pk Date: Sun, 27 Sep 2026 18:57:33 +0200 Subject: [PATCH] Improve item range selection. --- tp-table-item.js | 7 +++++++ tp-table.js | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/tp-table-item.js b/tp-table-item.js index 2296da9..72230d6 100644 --- a/tp-table-item.js +++ b/tp-table-item.js @@ -36,6 +36,13 @@ class TpTableItem extends BaseElement { padding: 0; } + /* Rows are click targets in row-click/ctrl-click selection modes — + a shift+click range gesture must not select their text. Checkbox + tables keep text selectable (selection lives in the checkbox). */ + :host([data-selection-mode]) { + user-select: none; + } + .wrap { display: grid; grid-template-columns: 0.5fr 3fr 0.5fr 0.5fr 0.5fr; /* Overridden by javascript */ diff --git a/tp-table.js b/tp-table.js index 071e664..da55be4 100644 --- a/tp-table.js +++ b/tp-table.js @@ -187,6 +187,7 @@ export class TpTable extends DomQuery(LitElement) { ${this.renderTableHeader(columns, showCheckboxColumn)}
this._selectionChanged(e)} + @mousedown=${(e) => this._listMousedown(e)} @click=${(e) => this._handleRowClick(e)}> ${!Array.isArray(items) || items.length === 0 ? html`
@@ -311,6 +312,7 @@ export class TpTable extends DomQuery(LitElement) { super(); this._selItems = new Map(); this._lastSelectedId = null; + this._shiftDownItem = null; this.selectionMode = 'checkbox'; } @@ -632,18 +634,42 @@ export class TpTable extends DomQuery(LitElement) { } } + // Text selection starts on MOUSEDOWN — preventing it at click is too + // late: the browser has already selected and the click may even retarget + // to a common ancestor instead of the row. Shift/ctrl/meta clicks are + // selection gestures in row-click mode, so the browser must not start a + // text selection from them. + _listMousedown(e) { + if (this.selectionMode !== 'row-click' || !this.selectable) return; + + if (e.shiftKey || e.ctrlKey || e.metaKey) { + e.preventDefault(); + } + + // Remember the row the shift gesture started on: if the click + // retargets (row re-rendered between mousedown and mouseup), its + // composed path no longer contains a row and the range would + // silently collapse to the previously selected rows. + this._shiftDownItem = e.shiftKey + ? e.composedPath().find(n => n?.hasAttribute && n.hasAttribute('item')) || null + : null; + } + _handleRowClick(e) { if (this.selectionMode !== 'row-click' || !this.selectable) return; - // Prevent the browser's native shift+click text selection since we're - // using shift+click for range selection. - e.preventDefault(); - // Walk through composedPath to find the host, since // lit-virtualizer puts rows in a shadow tree. - const itemEl = e.composedPath().find( + let itemEl = e.composedPath().find( n => n?.hasAttribute && n.hasAttribute('item') ); + + if (!itemEl && e.shiftKey && this._shiftDownItem) { + itemEl = this._shiftDownItem; + } + + this._shiftDownItem = null; + if (!itemEl || !itemEl.item) return; const id = this.getItemId(itemEl.item); @@ -661,6 +687,9 @@ export class TpTable extends DomQuery(LitElement) { const it = items[i]; this._selItems.set(this.getItemId(it), it); } + + // Stray text selection from before the gesture must not survive. + window.getSelection?.()?.removeAllRanges?.(); } } else if (e.ctrlKey || e.metaKey) { // Toggle individual row.