From fa39c21c3469e2be9bb3440f500c6fdd54070073 Mon Sep 17 00:00:00 2001 From: Cagatay Civici Date: Fri, 14 May 2021 13:21:59 +0300 Subject: [PATCH] Fixed #85 - Draggable Dialog --- api-generator/components/dialog.js | 26 ++++++- src/components/dialog/Dialog.d.ts | 4 ++ src/components/dialog/Dialog.vue | 111 ++++++++++++++++++++++++++++- src/views/dialog/DialogDoc.vue | 24 +++++++ 4 files changed, 162 insertions(+), 3 deletions(-) diff --git a/api-generator/components/dialog.js b/api-generator/components/dialog.js index 1b13bc3b5..89283f007 100644 --- a/api-generator/components/dialog.js +++ b/api-generator/components/dialog.js @@ -100,7 +100,31 @@ const DialogProps = [ type: "object", default: "null", description: "Object literal to define widths per screen size." - } + }, + { + name: "draggable", + type: "boolean", + default: "true", + description: "Whether the dialog can be displayed full screen." + }, + { + name: "minX", + type: "number", + default: "0", + description: "Minimum value for the left coordinate of dialog in dragging." + }, + { + name: "minY", + type: "number", + default: "0", + description: "Minimum value for the top coordinate of dialog in dragging." + }, + { + name: "keepInViewport", + type: "boolean", + default: "true", + description: "Keeps dialog in the viewport when dragging." + }, ]; const DialogEvents = [ diff --git a/src/components/dialog/Dialog.d.ts b/src/components/dialog/Dialog.d.ts index a4ce714ef..7e7351db1 100755 --- a/src/components/dialog/Dialog.d.ts +++ b/src/components/dialog/Dialog.d.ts @@ -18,6 +18,10 @@ interface DialogProps { position?: string; maximizable?: boolean; breakpoints?: {[key: string]: string}; + draggable: boolean; + keepInViewPort: boolean; + minX: number; + minY: number; } declare class Dialog { diff --git a/src/components/dialog/Dialog.vue b/src/components/dialog/Dialog.vue index 3b350c6fa..834d45856 100755 --- a/src/components/dialog/Dialog.vue +++ b/src/components/dialog/Dialog.vue @@ -3,7 +3,7 @@
-
+
{{header}} @@ -35,7 +35,7 @@ import Ripple from 'primevue/ripple'; export default { name: 'Dialog', inheritAttrs: false, - emits: ['update:visible','show','hide','maximize','unmaximize'], + emits: ['update:visible','show','hide','maximize','unmaximize','dragend'], props: { header: null, footer: null, @@ -77,6 +77,22 @@ export default { breakpoints: { type: Object, default: null + }, + draggable: { + type: Boolean, + default: true + }, + keepInViewport: { + type: Boolean, + default: true + }, + minX: { + type: Number, + default: 0 + }, + minY: { + type: Number, + default: 0 } }, data() { @@ -89,6 +105,11 @@ export default { container: null, mask: null, styleElement: null, + dragging: null, + documentDragListener: null, + documentDragEndListener: null, + lastPageX: null, + lastPageY: null, updated() { if (this.visible) { this.containerVisible = this.visible; @@ -96,6 +117,7 @@ export default { }, beforeUnmount() { this.unbindDocumentState(); + this.unbindGlobalListeners(); this.destroyStyle(); this.mask = null; @@ -127,11 +149,13 @@ export default { this.$emit('show'); this.focus(); this.enableDocumentSettings(); + this.bindGlobalListeners(); }, onBeforeLeave() { DomHandler.addClass(this.mask, 'p-dialog-mask-leave'); }, onLeave() { + this.$emit('hide'); }, onAfterLeave(el) { @@ -140,6 +164,7 @@ export default { } this.containerVisible = false; this.unbindDocumentState(); + this.unbindGlobalListeners(); }, onMaskClick(event) { if (this.dismissableMask && this.closable && this.modal && this.mask === event.target) { @@ -264,6 +289,88 @@ export default { document.head.removeChild(this.styleElement); this.styleElement = null; } + }, + initDrag(event) { + if (DomHandler.hasClass(event.target, 'p-dialog-header-icon') || DomHandler.hasClass(event.target.parentElement, 'p-dialog-header-icon')) { + return; + } + + if (this.draggable) { + this.dragging = true; + this.lastPageX = event.pageX; + this.lastPageY = event.pageY; + + this.container.style.margin = '0'; + DomHandler.addClass(document.body, 'p-unselectable-text'); + } + }, + bindGlobalListeners() { + if (this.draggable) { + this.bindDocumentDragListener(); + this.bindDocumentDragEndListener(); + } + }, + unbindGlobalListeners() { + this.unbindDocumentDragListener(); + this.unbindDocumentDragEndListener(); + }, + bindDocumentDragListener() { + this.documentDragListener = (event) => { + if (this.dragging) { + let width = DomHandler.getOuterWidth(this.container); + let height = DomHandler.getOuterHeight(this.container); + let deltaX = event.pageX - this.lastPageX; + let deltaY = event.pageY - this.lastPageY; + let offset = this.container.getBoundingClientRect(); + let leftPos = offset.left + deltaX; + let topPos = offset.top + deltaY; + let viewport = DomHandler.getViewport(); + + this.container.style.position = 'fixed'; + + if (this.keepInViewport) { + if (leftPos >= this.minX && (leftPos + width) < viewport.width) { + this.lastPageX = event.pageX; + this.container.style.left = leftPos + 'px'; + } + + if (topPos >= this.minY && (topPos + height) < viewport.height) { + this.lastPageY = event.pageY; + this.container.style.top = topPos + 'px'; + } + } + else { + this.lastPageX = event.pageX; + this.container.style.left = leftPos + 'px'; + this.lastPageY = event.pageY; + this.container.style.top = topPos + 'px'; + } + } + } + window.document.addEventListener('mousemove', this.documentDragListener); + }, + unbindDocumentDragListener() { + if (this.documentDragListener) { + window.document.removeEventListener('mousemove', this.documentDragListener); + this.documentDragListener = null; + } + }, + bindDocumentDragEndListener() { + this.documentDragEndListener = (event) => { + if (this.dragging) { + this.dragging = false; + DomHandler.removeClass(document.body, 'p-unselectable-text'); + + this.$emit('dragend', event); + } + }; + window.document.addEventListener('mouseup', this.documentDragEndListener); + }, + unbindDocumentDragEndListener() { + if (this.documentDragEndListener) { + window.document.removeEventListener('mouseup', this.documentDragEndListener); + this.documentDragEndListener = null; + } } }, computed: { diff --git a/src/views/dialog/DialogDoc.vue b/src/views/dialog/DialogDoc.vue index 50c30f6b8..e9bd595e3 100755 --- a/src/views/dialog/DialogDoc.vue +++ b/src/views/dialog/DialogDoc.vue @@ -197,6 +197,30 @@ export default { object null Object literal to define widths per screen size. + + + draggable + boolean + true + Enables dragging to change the position using header. + + + minX + number + 0 + Minimum value for the left coordinate of dialog in dragging. + + + minY + number + 0 + Minimum value for the top coordinate of dialog in dragging. + + + keepInViewport + boolean + true + Keeps dialog in the viewport when dragging.