Fixed #85 - Draggable Dialog

pull/1196/head^2
Cagatay Civici 2021-05-14 13:21:59 +03:00
parent 2acf800b43
commit fa39c21c34
4 changed files with 162 additions and 3 deletions

View File

@ -100,7 +100,31 @@ const DialogProps = [
type: "object", type: "object",
default: "null", default: "null",
description: "Object literal to define widths per screen size." 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 = [ const DialogEvents = [

View File

@ -18,6 +18,10 @@ interface DialogProps {
position?: string; position?: string;
maximizable?: boolean; maximizable?: boolean;
breakpoints?: {[key: string]: string}; breakpoints?: {[key: string]: string};
draggable: boolean;
keepInViewPort: boolean;
minX: number;
minY: number;
} }
declare class Dialog { declare class Dialog {

View File

@ -3,7 +3,7 @@
<div :ref="maskRef" :class="maskClass" v-if="containerVisible" @click="onMaskClick"> <div :ref="maskRef" :class="maskClass" v-if="containerVisible" @click="onMaskClick">
<transition name="p-dialog" @before-enter="onBeforeEnter" @enter="onEnter" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" appear> <transition name="p-dialog" @before-enter="onBeforeEnter" @enter="onEnter" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" appear>
<div :ref="containerRef" :class="dialogClass" v-if="visible" v-bind="$attrs" role="dialog" :aria-labelledby="ariaLabelledById" :aria-modal="modal"> <div :ref="containerRef" :class="dialogClass" v-if="visible" v-bind="$attrs" role="dialog" :aria-labelledby="ariaLabelledById" :aria-modal="modal">
<div class="p-dialog-header" v-if="showHeader"> <div class="p-dialog-header" v-if="showHeader" @mousedown="initDrag">
<slot name="header"> <slot name="header">
<span :id="ariaLabelledById" class="p-dialog-title" v-if="header">{{header}}</span> <span :id="ariaLabelledById" class="p-dialog-title" v-if="header">{{header}}</span>
</slot> </slot>
@ -35,7 +35,7 @@ import Ripple from 'primevue/ripple';
export default { export default {
name: 'Dialog', name: 'Dialog',
inheritAttrs: false, inheritAttrs: false,
emits: ['update:visible','show','hide','maximize','unmaximize'], emits: ['update:visible','show','hide','maximize','unmaximize','dragend'],
props: { props: {
header: null, header: null,
footer: null, footer: null,
@ -77,6 +77,22 @@ export default {
breakpoints: { breakpoints: {
type: Object, type: Object,
default: null default: null
},
draggable: {
type: Boolean,
default: true
},
keepInViewport: {
type: Boolean,
default: true
},
minX: {
type: Number,
default: 0
},
minY: {
type: Number,
default: 0
} }
}, },
data() { data() {
@ -89,6 +105,11 @@ export default {
container: null, container: null,
mask: null, mask: null,
styleElement: null, styleElement: null,
dragging: null,
documentDragListener: null,
documentDragEndListener: null,
lastPageX: null,
lastPageY: null,
updated() { updated() {
if (this.visible) { if (this.visible) {
this.containerVisible = this.visible; this.containerVisible = this.visible;
@ -96,6 +117,7 @@ export default {
}, },
beforeUnmount() { beforeUnmount() {
this.unbindDocumentState(); this.unbindDocumentState();
this.unbindGlobalListeners();
this.destroyStyle(); this.destroyStyle();
this.mask = null; this.mask = null;
@ -127,11 +149,13 @@ export default {
this.$emit('show'); this.$emit('show');
this.focus(); this.focus();
this.enableDocumentSettings(); this.enableDocumentSettings();
this.bindGlobalListeners();
}, },
onBeforeLeave() { onBeforeLeave() {
DomHandler.addClass(this.mask, 'p-dialog-mask-leave'); DomHandler.addClass(this.mask, 'p-dialog-mask-leave');
}, },
onLeave() { onLeave() {
this.$emit('hide'); this.$emit('hide');
}, },
onAfterLeave(el) { onAfterLeave(el) {
@ -140,6 +164,7 @@ export default {
} }
this.containerVisible = false; this.containerVisible = false;
this.unbindDocumentState(); this.unbindDocumentState();
this.unbindGlobalListeners();
}, },
onMaskClick(event) { onMaskClick(event) {
if (this.dismissableMask && this.closable && this.modal && this.mask === event.target) { if (this.dismissableMask && this.closable && this.modal && this.mask === event.target) {
@ -264,6 +289,88 @@ export default {
document.head.removeChild(this.styleElement); document.head.removeChild(this.styleElement);
this.styleElement = null; 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: { computed: {

View File

@ -197,6 +197,30 @@ export default {
<td>object</td> <td>object</td>
<td>null</td> <td>null</td>
<td>Object literal to define widths per screen size.</td> <td>Object literal to define widths per screen size.</td>
</tr>
<tr>
<td>draggable</td>
<td>boolean</td>
<td>true</td>
<td>Enables dragging to change the position using header.</td>
</tr>
<tr>
<td>minX</td>
<td>number</td>
<td>0</td>
<td>Minimum value for the left coordinate of dialog in dragging.</td>
</tr>
<tr>
<td>minY</td>
<td>number</td>
<td>0</td>
<td>Minimum value for the top coordinate of dialog in dragging.</td>
</tr>
<tr>
<td>keepInViewport</td>
<td>boolean</td>
<td>true</td>
<td>Keeps dialog in the viewport when dragging.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>