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.