Only clip/scroll once the dialog has been explicitly sized via resize
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tp/tp-dialog",
|
"name": "@tp/tp-dialog",
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "tp-dialog.js",
|
"main": "tp-dialog.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+149
-9
@@ -45,6 +45,7 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
|
|
||||||
dialog {
|
dialog {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
border-radius: var(--tp-dialog-border-radius);
|
border-radius: var(--tp-dialog-border-radius);
|
||||||
background-color: var(--tp-dialog-bg);
|
background-color: var(--tp-dialog-bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -52,6 +53,28 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
padding: var(--tp-dialog-padding);
|
padding: var(--tp-dialog-padding);
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
transform: translate(var(--tp-dialog-offset-x, 0px), var(--tp-dialog-offset-y, 0px));
|
transform: translate(var(--tp-dialog-offset-x, 0px), var(--tp-dialog-offset-y, 0px));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog:not([open]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only clip/scroll once the dialog has been explicitly sized via resize. */
|
||||||
|
:host([constrained]) dialog {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([constrained]) .scroll-wrapper {
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-icon {
|
.close-icon {
|
||||||
@@ -72,12 +95,26 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
cursor: move;
|
cursor: move;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
cursor: se-resize;
|
||||||
|
z-index: 4;
|
||||||
|
background-image: linear-gradient(135deg, transparent 0%, transparent 50%, var(--tp-dialog-resize-handle-color, #888) 50%, var(--tp-dialog-resize-handle-color, #888) 60%, transparent 60%, transparent 70%, var(--tp-dialog-resize-handle-color, #888) 70%, var(--tp-dialog-resize-handle-color, #888) 80%, transparent 80%);
|
||||||
|
background-size: 8px 8px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom right;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showClose, unmovable } = this;
|
const { showClose, unmovable, resizable } = this;
|
||||||
return html`
|
return html`
|
||||||
<dialog part="dialog">
|
<dialog part="dialog">
|
||||||
${!unmovable ? html`
|
${!unmovable ? html`
|
||||||
@@ -88,7 +125,12 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
` : null}
|
` : null}
|
||||||
|
<div class="scroll-wrapper" part="scroll-wrapper">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
${resizable ? html`
|
||||||
|
<div class="resize-handle" part="resize-handle" @pointerdown=${this._onResizeStart}></div>
|
||||||
|
` : null}
|
||||||
</dialog>
|
</dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -102,6 +144,7 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
closeOnOutsideClick: { type: Boolean },
|
closeOnOutsideClick: { type: Boolean },
|
||||||
modal: { type: Boolean, reflect: true },
|
modal: { type: Boolean, reflect: true },
|
||||||
unmovable: { type: Boolean, reflect: true },
|
unmovable: { type: Boolean, reflect: true },
|
||||||
|
resizable: { type: Boolean, reflect: true },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +162,7 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
this._resolvePromise = null;
|
this._resolvePromise = null;
|
||||||
this.modal = false;
|
this.modal = false;
|
||||||
this.unmovable = false;
|
this.unmovable = false;
|
||||||
|
this.resizable = false;
|
||||||
this._offsetX = 0;
|
this._offsetX = 0;
|
||||||
this._offsetY = 0;
|
this._offsetY = 0;
|
||||||
}
|
}
|
||||||
@@ -138,14 +182,8 @@ 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
|
// Clean up pointer drag/resize listeners
|
||||||
if (this._boundDragMove) {
|
this._cleanupActiveListeners();
|
||||||
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();
|
||||||
@@ -209,6 +247,13 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
// Remove from dialog stack
|
// Remove from dialog stack
|
||||||
this._removeFromDialogStack();
|
this._removeFromDialogStack();
|
||||||
|
|
||||||
|
// Clean up pointer drag/resize listeners if they are currently active
|
||||||
|
this._cleanupActiveListeners();
|
||||||
|
|
||||||
|
if (this.closeOnOutsideClick) {
|
||||||
|
this.removeEventListener('click', this._handleOutsideClick);
|
||||||
|
}
|
||||||
|
|
||||||
// If closed without explicit confirm/dismiss (like ESC key), treat as dismissed
|
// If closed without explicit confirm/dismiss (like ESC key), treat as dismissed
|
||||||
if (this._currentPromise && this._resolvePromise) {
|
if (this._currentPromise && this._resolvePromise) {
|
||||||
this._resolvePromise('dismissed');
|
this._resolvePromise('dismissed');
|
||||||
@@ -217,11 +262,38 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_cleanupActiveListeners() {
|
||||||
|
if (this._boundDragMove) {
|
||||||
|
window.removeEventListener('pointermove', this._boundDragMove);
|
||||||
|
this._boundDragMove = null;
|
||||||
|
}
|
||||||
|
if (this._boundDragEnd) {
|
||||||
|
window.removeEventListener('pointerup', this._boundDragEnd);
|
||||||
|
window.removeEventListener('pointercancel', this._boundDragEnd);
|
||||||
|
this._boundDragEnd = null;
|
||||||
|
}
|
||||||
|
if (this._boundResizeMove) {
|
||||||
|
window.removeEventListener('pointermove', this._boundResizeMove);
|
||||||
|
this._boundResizeMove = null;
|
||||||
|
}
|
||||||
|
if (this._boundResizeEnd) {
|
||||||
|
window.removeEventListener('pointerup', this._boundResizeEnd);
|
||||||
|
window.removeEventListener('pointercancel', this._boundResizeEnd);
|
||||||
|
this._boundResizeEnd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_resetPosition() {
|
_resetPosition() {
|
||||||
this._offsetX = 0;
|
this._offsetX = 0;
|
||||||
this._offsetY = 0;
|
this._offsetY = 0;
|
||||||
this.style.removeProperty('--tp-dialog-offset-x');
|
this.style.removeProperty('--tp-dialog-offset-x');
|
||||||
this.style.removeProperty('--tp-dialog-offset-y');
|
this.style.removeProperty('--tp-dialog-offset-y');
|
||||||
|
this.removeAttribute('constrained');
|
||||||
|
const dialogEl = this.dialog;
|
||||||
|
if (dialogEl) {
|
||||||
|
dialogEl.style.removeProperty('width');
|
||||||
|
dialogEl.style.removeProperty('height');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDragStart(e) {
|
_onDragStart(e) {
|
||||||
@@ -269,6 +341,74 @@ class TpDialog extends EventHelpers(LitElement) {
|
|||||||
window.removeEventListener('pointercancel', this._boundDragEnd);
|
window.removeEventListener('pointercancel', this._boundDragEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onResizeStart(e) {
|
||||||
|
if (!this.resizable) return;
|
||||||
|
if (e.button !== 0) return; // Only resize with left/main pointer button
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Once the user starts resizing, content must scroll inside the fixed box.
|
||||||
|
this.setAttribute('constrained', '');
|
||||||
|
|
||||||
|
const rect = this.dialog.getBoundingClientRect();
|
||||||
|
this._startWidth = rect.width;
|
||||||
|
this._startHeight = rect.height;
|
||||||
|
this._startX = e.clientX;
|
||||||
|
this._startY = e.clientY;
|
||||||
|
this._startOffsetX = this._offsetX || 0;
|
||||||
|
this._startOffsetY = this._offsetY || 0;
|
||||||
|
|
||||||
|
const resizeHandle = this.shadowRoot.querySelector('.resize-handle');
|
||||||
|
if (resizeHandle && typeof resizeHandle.setPointerCapture === 'function') {
|
||||||
|
resizeHandle.setPointerCapture(e.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._boundResizeMove = this._onResizeMove.bind(this);
|
||||||
|
this._boundResizeEnd = this._onResizeEnd.bind(this);
|
||||||
|
|
||||||
|
window.addEventListener('pointermove', this._boundResizeMove);
|
||||||
|
window.addEventListener('pointerup', this._boundResizeEnd);
|
||||||
|
window.addEventListener('pointercancel', this._boundResizeEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onResizeMove(e) {
|
||||||
|
const dw = e.clientX - this._startX;
|
||||||
|
const dh = e.clientY - this._startY;
|
||||||
|
|
||||||
|
const newWidth = Math.max(150, this._startWidth + dw);
|
||||||
|
const newHeight = Math.max(100, this._startHeight + dh);
|
||||||
|
|
||||||
|
const dw_actual = newWidth - this._startWidth;
|
||||||
|
const dh_actual = newHeight - this._startHeight;
|
||||||
|
|
||||||
|
this._offsetX = this._startOffsetX + dw_actual / 2;
|
||||||
|
this._offsetY = this._startOffsetY + dh_actual / 2;
|
||||||
|
|
||||||
|
const originalTop = (window.innerHeight - newHeight) / 2;
|
||||||
|
if (originalTop + this._offsetY < 0) {
|
||||||
|
this._offsetY = -originalTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dialog.style.width = `${newWidth}px`;
|
||||||
|
this.dialog.style.height = `${newHeight}px`;
|
||||||
|
this.style.setProperty('--tp-dialog-offset-x', `${this._offsetX}px`);
|
||||||
|
this.style.setProperty('--tp-dialog-offset-y', `${this._offsetY}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onResizeEnd(e) {
|
||||||
|
const resizeHandle = this.shadowRoot.querySelector('.resize-handle');
|
||||||
|
if (resizeHandle && typeof resizeHandle.releasePointerCapture === 'function') {
|
||||||
|
try {
|
||||||
|
resizeHandle.releasePointerCapture(e.pointerId);
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('pointermove', this._boundResizeMove);
|
||||||
|
window.removeEventListener('pointerup', this._boundResizeEnd);
|
||||||
|
window.removeEventListener('pointercancel', this._boundResizeEnd);
|
||||||
|
}
|
||||||
|
|
||||||
_handleConfirmed(event) {
|
_handleConfirmed(event) {
|
||||||
if (this._currentPromise && this._resolvePromise) {
|
if (this._currentPromise && this._resolvePromise) {
|
||||||
this._resolvePromise('confirmed');
|
this._resolvePromise('confirmed');
|
||||||
|
|||||||
Reference in New Issue
Block a user