From 68fa2427dc5bb0186fb67d1540a14d6ca5f38db1 Mon Sep 17 00:00:00 2001 From: pk Date: Thu, 18 Jun 2026 20:51:08 +0200 Subject: [PATCH] Movable and click-through for non-modals --- tp-dialog.js | 94 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/tp-dialog.js b/tp-dialog.js index 0f8e4f9..a14058b 100644 --- a/tp-dialog.js +++ b/tp-dialog.js @@ -39,7 +39,7 @@ class TpDialog extends EventHelpers(LitElement) { z-index: 900; } - :host([open]) { + :host([open][modal]) { pointer-events: all; } @@ -50,24 +50,39 @@ class TpDialog extends EventHelpers(LitElement) { color: var(--text); border: var(--tp-dialog-border); padding: var(--tp-dialog-padding); + pointer-events: all; + transform: translate(var(--tp-dialog-offset-x, 0px), var(--tp-dialog-offset-y, 0px)); } .close-icon { position: absolute; right: 4px; top: 5px; - z-index: 1; + z-index: 3; --tp-icon-width: 18px; --tp-icon-height: 18px; } + + .drag-handle { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 30px; + cursor: move; + z-index: 2; + } ` ]; } render() { - const { showClose } = this; + const { showClose, unmovable } = this; return html` + ${!unmovable ? html` +
+ ` : null} ${showClose ? html`
@@ -85,6 +100,8 @@ class TpDialog extends EventHelpers(LitElement) { icon: { type: Object }, closeOnEsc: { type: Boolean }, closeOnOutsideClick: { type: Boolean }, + modal: { type: Boolean, reflect: true }, + unmovable: { type: Boolean, reflect: true }, }; } @@ -100,6 +117,10 @@ class TpDialog extends EventHelpers(LitElement) { super(); this._currentPromise = null; this._resolvePromise = null; + this.modal = false; + this.unmovable = false; + this._offsetX = 0; + this._offsetY = 0; } connectedCallback() { @@ -117,6 +138,15 @@ class TpDialog extends EventHelpers(LitElement) { this.unlisten(this, 'confirmed', '_handleConfirmed'); this.unlisten(this, 'dismissed', '_handleDismissed'); + // Clean up pointer drag listeners + if (this._boundDragMove) { + window.removeEventListener('pointermove', this._boundDragMove); + } + if (this._boundDragEnd) { + window.removeEventListener('pointerup', this._boundDragEnd); + window.removeEventListener('pointercancel', this._boundDragEnd); + } + // Remove this dialog from the stack this._removeFromDialogStack(); @@ -129,6 +159,8 @@ class TpDialog extends EventHelpers(LitElement) { } show() { + this.modal = false; + this._resetPosition(); this.dialog.show(); this.open = true; @@ -144,6 +176,8 @@ class TpDialog extends EventHelpers(LitElement) { } showModal() { + this.modal = true; + this._resetPosition(); this.dialog.showModal(); this.open = true; @@ -183,6 +217,58 @@ class TpDialog extends EventHelpers(LitElement) { } } + _resetPosition() { + this._offsetX = 0; + this._offsetY = 0; + this.style.removeProperty('--tp-dialog-offset-x'); + this.style.removeProperty('--tp-dialog-offset-y'); + } + + _onDragStart(e) { + if (this.unmovable) return; + if (e.button !== 0) return; // Only drag with left/main pointer button + + this._startX = e.clientX; + this._startY = e.clientY; + this._startOffsetX = this._offsetX || 0; + this._startOffsetY = this._offsetY || 0; + + const dragHandle = this.shadowRoot.querySelector('.drag-handle'); + if (dragHandle && typeof dragHandle.setPointerCapture === 'function') { + dragHandle.setPointerCapture(e.pointerId); + } + + this._boundDragMove = this._onDragMove.bind(this); + this._boundDragEnd = this._onDragEnd.bind(this); + + window.addEventListener('pointermove', this._boundDragMove); + window.addEventListener('pointerup', this._boundDragEnd); + window.addEventListener('pointercancel', this._boundDragEnd); + } + + _onDragMove(e) { + const dx = e.clientX - this._startX; + const dy = e.clientY - this._startY; + this._offsetX = this._startOffsetX + dx; + this._offsetY = this._startOffsetY + dy; + + this.style.setProperty('--tp-dialog-offset-x', `${this._offsetX}px`); + this.style.setProperty('--tp-dialog-offset-y', `${this._offsetY}px`); + } + + _onDragEnd(e) { + const dragHandle = this.shadowRoot.querySelector('.drag-handle'); + if (dragHandle && typeof dragHandle.releasePointerCapture === 'function') { + try { + dragHandle.releasePointerCapture(e.pointerId); + } catch (err) {} + } + + window.removeEventListener('pointermove', this._boundDragMove); + window.removeEventListener('pointerup', this._boundDragEnd); + window.removeEventListener('pointercancel', this._boundDragEnd); + } + _handleConfirmed(event) { if (this._currentPromise && this._resolvePromise) { this._resolvePromise('confirmed'); @@ -267,4 +353,4 @@ class TpDialog extends EventHelpers(LitElement) { } } -window.customElements.define('tp-dialog', TpDialog); +window.customElements.define('tp-dialog', TpDialog); \ No newline at end of file