Movable and click-through for non-modals

This commit is contained in:
2026-06-18 20:51:08 +02:00
parent 55c7a5ef8e
commit 68fa2427dc
+90 -4
View File
@@ -39,7 +39,7 @@ class TpDialog extends EventHelpers(LitElement) {
z-index: 900; z-index: 900;
} }
:host([open]) { :host([open][modal]) {
pointer-events: all; pointer-events: all;
} }
@@ -50,24 +50,39 @@ class TpDialog extends EventHelpers(LitElement) {
color: var(--text); color: var(--text);
border: var(--tp-dialog-border); border: var(--tp-dialog-border);
padding: var(--tp-dialog-padding); padding: var(--tp-dialog-padding);
pointer-events: all;
transform: translate(var(--tp-dialog-offset-x, 0px), var(--tp-dialog-offset-y, 0px));
} }
.close-icon { .close-icon {
position: absolute; position: absolute;
right: 4px; right: 4px;
top: 5px; top: 5px;
z-index: 1; z-index: 3;
--tp-icon-width: 18px; --tp-icon-width: 18px;
--tp-icon-height: 18px; --tp-icon-height: 18px;
} }
.drag-handle {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30px;
cursor: move;
z-index: 2;
}
` `
]; ];
} }
render() { render() {
const { showClose } = this; const { showClose, unmovable } = this;
return html` return html`
<dialog part="dialog"> <dialog part="dialog">
${!unmovable ? html`
<div class="drag-handle" part="drag-handle" @pointerdown=${this._onDragStart}></div>
` : null}
${showClose ? html` ${showClose ? html`
<div class="close-icon" part="close-icon"> <div class="close-icon" part="close-icon">
<tp-icon .icon=${this.icon ? this.icon : TpDialog.closeIcon} dialog-dismiss></tp-icon> <tp-icon .icon=${this.icon ? this.icon : TpDialog.closeIcon} dialog-dismiss></tp-icon>
@@ -85,6 +100,8 @@ class TpDialog extends EventHelpers(LitElement) {
icon: { type: Object }, icon: { type: Object },
closeOnEsc: { type: Boolean }, closeOnEsc: { type: Boolean },
closeOnOutsideClick: { 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(); super();
this._currentPromise = null; this._currentPromise = null;
this._resolvePromise = null; this._resolvePromise = null;
this.modal = false;
this.unmovable = false;
this._offsetX = 0;
this._offsetY = 0;
} }
connectedCallback() { connectedCallback() {
@@ -117,6 +138,15 @@ class TpDialog extends EventHelpers(LitElement) {
this.unlisten(this, 'confirmed', '_handleConfirmed'); this.unlisten(this, 'confirmed', '_handleConfirmed');
this.unlisten(this, 'dismissed', '_handleDismissed'); 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 // Remove this dialog from the stack
this._removeFromDialogStack(); this._removeFromDialogStack();
@@ -129,6 +159,8 @@ class TpDialog extends EventHelpers(LitElement) {
} }
show() { show() {
this.modal = false;
this._resetPosition();
this.dialog.show(); this.dialog.show();
this.open = true; this.open = true;
@@ -144,6 +176,8 @@ class TpDialog extends EventHelpers(LitElement) {
} }
showModal() { showModal() {
this.modal = true;
this._resetPosition();
this.dialog.showModal(); this.dialog.showModal();
this.open = true; 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) { _handleConfirmed(event) {
if (this._currentPromise && this._resolvePromise) { if (this._currentPromise && this._resolvePromise) {
this._resolvePromise('confirmed'); this._resolvePromise('confirmed');
@@ -267,4 +353,4 @@ class TpDialog extends EventHelpers(LitElement) {
} }
} }
window.customElements.define('tp-dialog', TpDialog); window.customElements.define('tp-dialog', TpDialog);