Skip to content

Commit

Permalink
Fixed #1616 - Add draggable and resizable features to Dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
mertsincan committed Feb 17, 2021
1 parent 2928746 commit 7cb96d7
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 10 deletions.
17 changes: 17 additions & 0 deletions src/components/dialog/Dialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
pointer-events: auto;
max-height: 90%;
transform: scale(1);
position: relative;
}

.p-dialog-content {
Expand Down Expand Up @@ -202,3 +203,19 @@
display: flex;
align-items: center;
}

/* Resizable */
.p-dialog .p-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}

.p-dialog-resizable .p-dialog-header {
cursor: move;
}
237 changes: 227 additions & 10 deletions src/components/dialog/Dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export class Dialog extends Component {
footer: null,
visible: false,
position: 'center',
draggable: true,
resizable: true,
modal: true,
onHide: null,
onShow: null,
Expand All @@ -36,8 +38,17 @@ export class Dialog extends Component {
icons: null,
ariaCloseIconLabel: 'Close',
focusOnShow: true,
minX: 0,
minY: 0,
keepInViewport: true,
maximized: false,
onMaximize: null
onMaximize: null,
onDragStart: null,
onDrag: null,
onDragEnd: null,
onResizeStart: null,
onResize: null,
onResizeEnd: null
}

static propTypes = {
Expand All @@ -46,6 +57,8 @@ export class Dialog extends Component {
footer: PropTypes.any,
visible: PropTypes.bool,
position: PropTypes.string,
draggable: PropTypes.bool,
resizable: PropTypes.bool,
modal: PropTypes.bool,
onHide: PropTypes.func.isRequired,
onShow: PropTypes.func,
Expand All @@ -66,8 +79,17 @@ export class Dialog extends Component {
icons: PropTypes.any,
ariaCloseIconLabel: PropTypes.string,
focusOnShow: PropTypes.bool,
minX: PropTypes.number,
minY: PropTypes.number,
keepInViewport: PropTypes.bool,
maximized: PropTypes.bool,
onMaximize: PropTypes.func
onMaximize: PropTypes.func,
onDragStart: PropTypes.func,
onDrag: PropTypes.func,
onDragEnd: PropTypes.func,
onResizeStart: PropTypes.func,
onResize: PropTypes.func,
onResizeEnd: PropTypes.func
};

constructor(props) {
Expand All @@ -83,6 +105,8 @@ export class Dialog extends Component {

this.onClose = this.onClose.bind(this);
this.toggleMaximize = this.toggleMaximize.bind(this);
this.onDragStart = this.onDragStart.bind(this);
this.onResizeStart = this.onResizeStart.bind(this);
this.onMaskClick = this.onMaskClick.bind(this);
this.onEntered = this.onEntered.bind(this);
this.onExited = this.onExited.bind(this);
Expand Down Expand Up @@ -128,6 +152,142 @@ export class Dialog extends Component {
event.preventDefault();
}

onDragStart(event) {
if (DomHandler.hasClass(event.target, 'p-dialog-header-icon') || DomHandler.hasClass(event.target.parentElement, 'p-dialog-header-icon')) {
return;
}

if (this.props.draggable) {
this.dragging = true;
this.lastPageX = event.pageX;
this.lastPageY = event.pageY;

this.dialogEl.style.margin = '0';
DomHandler.addClass(document.body, 'p-unselectable-text');

if (this.props.onDragStart) {
this.props.onDragStart(event);
}
}
}

onDrag(event) {
if (this.dragging) {
let width = DomHandler.getOuterWidth(this.dialogEl);
let height = DomHandler.getOuterHeight(this.dialogEl);
let deltaX = event.pageX - this.lastPageX;
let deltaY = event.pageY - this.lastPageY;
let offset = DomHandler.getOffset(this.dialogEl);
let leftPos = offset.left + deltaX;
let topPos = offset.top + deltaY;
let viewport = DomHandler.getViewport();

this.dialogEl.style.position = 'fixed';

if (this.props.keepInViewport) {
if (leftPos >= this.props.minX && (leftPos + width) < viewport.width) {
this.lastPageX = event.pageX;
this.dialogEl.style.left = leftPos + 'px';
}

if (topPos >= this.props.minY && (topPos + height) < viewport.height) {
this.lastPageY = event.pageY;
this.dialogEl.style.top = topPos + 'px';
}
}
else {
this.lastPageX = event.pageX;
this.dialogEl.style.left = leftPos + 'px';
this.lastPageY = event.pageY;
this.dialogEl.style.top = topPos + 'px';
}

if (this.props.onDrag) {
this.props.onDrag(event);
}
}
}

onDragEnd(event) {
if (this.dragging) {
this.dragging = false;
DomHandler.removeClass(document.body, 'p-unselectable-text');

if (this.props.onDragEnd) {
this.props.onDragEnd(event);
}
}
}

onResizeStart(event) {
if (this.props.resizable) {
this.resizing = true;
this.lastPageX = event.pageX;
this.lastPageY = event.pageY;
DomHandler.addClass(document.body, 'p-unselectable-text');

if (this.props.onResizeStart) {
this.props.onResizeStart(event);
}
}
}

onResize(event) {
if (this.resizing) {
let deltaX = event.pageX - this.lastPageX;
let deltaY = event.pageY - this.lastPageY;
let width = DomHandler.getOuterWidth(this.dialogEl);
let height = DomHandler.getOuterHeight(this.dialogEl);
let contentHeight = DomHandler.getOuterHeight(this.contentEl);
let newWidth = width + deltaX;
let newHeight = height + deltaY;
let minWidth = this.dialogEl.style.minWidth;
let minHeight = this.dialogEl.style.minHeight;
let offset = DomHandler.getOffset(this.dialogEl);
let viewport = DomHandler.getViewport();
let hasBeenDragged = !parseInt(this.dialogEl.style.top) || !parseInt(this.dialogEl.style.left);

if (hasBeenDragged) {
newWidth += deltaX;
newHeight += deltaY;
}

if ((!minWidth || newWidth > parseInt(minWidth)) && (offset.left + newWidth) < viewport.width) {
this.dialogEl.style.width = newWidth + 'px';
}

if ((!minHeight || newHeight > parseInt(minHeight)) && (offset.top + newHeight) < viewport.height) {
this.contentEl.style.height = contentHeight + newHeight - height + 'px';
this.dialogEl.style.height = newHeight + 'px';
}

this.lastPageX = event.pageX;
this.lastPageY = event.pageY;

if (this.props.onResize) {
this.props.onResize(event);
}
}
}

onResizeEnd(event) {
if (this.resizing) {
this.resizing = false;
DomHandler.removeClass(document.body, 'p-unselectable-text');

if (this.props.onResizeEnd) {
this.props.onResizeEnd(event);
}
}
}

resetPosition() {
this.dialogEl.style.position = '';
this.dialogEl.style.left = '';
this.dialogEl.style.top = '';
this.dialogEl.style.margin = '';
}

getPositionClass() {
const positions = ['center', 'left', 'right', 'top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right'];
const pos = positions.find(item => item === this.props.position || item.replace('-', '') === this.props.position);
Expand All @@ -143,6 +303,10 @@ export class Dialog extends Component {
return this.props.onMaximize ? this.props.maximized : this.state.maximized;
}

get dialogEl() {
return this.dialogRef.current;
}

onEntered() {
if (this.props.onShow) {
this.props.onShow();
Expand All @@ -156,24 +320,23 @@ export class Dialog extends Component {
}

onExited() {
this.dragging = false;
this.setState({ maskVisible: false });
this.disableDocumentSettings();
}

enableDocumentSettings() {
if (this.props.modal) {
this.bindGlobalListeners();
}
this.bindGlobalListeners();

if (this.props.blockScroll || (this.props.maximizable && this.maximized)) {
DomHandler.addClass(document.body, 'p-overflow-hidden');
}
}

disableDocumentSettings() {
if (this.props.modal) {
this.unbindGlobalListeners();
this.unbindGlobalListeners();

if (this.props.modal) {
let hasBlockScroll = document.primeDialogParams && document.primeDialogParams.some(param => param.hasBlockScroll);
if (!hasBlockScroll) {
DomHandler.removeClass(document.body, 'p-overflow-hidden');
Expand All @@ -185,15 +348,57 @@ export class Dialog extends Component {
}

bindGlobalListeners() {
if (this.props.draggable) {
this.bindDocumentDragListener();
}

if (this.props.resizable) {
this.bindDocumentResizeListeners();
}

if (this.props.closeOnEscape && this.props.closable) {
this.bindDocumentKeyDownListener();
}
}

unbindGlobalListeners() {
this.unbindDocumentDragListener();
this.unbindDocumentResizeListeners();
this.unbindDocumentKeyDownListener();
}

bindDocumentDragListener() {
this.documentDragListener = this.onDrag.bind(this);
this.documentDragEndListener = this.onDragEnd.bind(this);
window.document.addEventListener('mousemove', this.documentDragListener);
window.document.addEventListener('mouseup', this.documentDragEndListener);
}

unbindDocumentDragListener() {
if (this.documentDragListener && this.documentDragEndListener) {
window.document.removeEventListener('mousemove', this.documentDragListener);
window.document.removeEventListener('mouseup', this.documentDragEndListener);
this.documentDragListener = null;
this.documentDragEndListener = null;
}
}

bindDocumentResizeListeners() {
this.documentResizeListener = this.onResize.bind(this);
this.documentResizeEndListener = this.onResizeEnd.bind(this);
window.document.addEventListener('mousemove', this.documentResizeListener);
window.document.addEventListener('mouseup', this.documentResizeEndListener);
}

unbindDocumentResizeListeners() {
if (this.documentResizeListener && this.documentResizeEndListener) {
window.document.removeEventListener('mousemove', this.documentResizeListener);
window.document.removeEventListener('mouseup', this.documentResizeEndListener);
this.documentResizeListener = null;
this.documentResizeEndListener = null;
}
}

bindDocumentKeyDownListener() {
this.documentKeyDownListener = (event) => {
let currentTarget = event.currentTarget;
Expand Down Expand Up @@ -327,7 +532,7 @@ export class Dialog extends Component {
const header = ObjectUtils.getJSXElement(this.props.header, this.props);

return (
<div ref={el => this.headerElement = el} className="p-dialog-header">
<div ref={el => this.headerEl = el} className="p-dialog-header" onMouseDown={this.onDragStart}>
<span id={this.id + '_header'} className="p-dialog-title">{header}</span>
<div className="p-dialog-header-icons">
{icons}
Expand All @@ -345,7 +550,7 @@ export class Dialog extends Component {
let contentClassName = classNames('p-dialog-content', this.props.contentClassName)

return (
<div ref={el => this.contentElement = el} className={contentClassName} style={this.props.contentStyle}>
<div ref={el => this.contentEl = el} className={contentClassName} style={this.props.contentStyle}>
{this.props.children}
</div>
);
Expand All @@ -357,6 +562,14 @@ export class Dialog extends Component {
return footer && <div ref={el => this.footerElement = el} className="p-dialog-footer">{footer}</div>
}

renderResizer() {
if (this.props.resizable) {
return <div className="p-resizable-handle" style={{zIndex: 90}} onMouseDown={this.onResizeStart}></div>
}

return null;
}

renderElement() {
const className = classNames('p-dialog p-component', this.props.className, {
'p-dialog-rtl': this.props.rtl,
Expand All @@ -365,12 +578,15 @@ export class Dialog extends Component {

const maskClassName = classNames('p-dialog-mask', {
'p-component-overlay': this.props.modal,
'p-dialog-visible': this.state.maskVisible
'p-dialog-visible': this.state.maskVisible,
'p-dialog-draggable': this.props.draggable,
'p-dialog-resizable': this.props.resizable,
}, this.props.maskClassName, this.getPositionClass());

const header = this.renderHeader();
const content = this.renderContent();
const footer = this.renderFooter();
const resizer = this.renderResizer();

let transitionTimeout = {
enter: this.props.position === 'center' ? 150 : 300,
Expand All @@ -386,6 +602,7 @@ export class Dialog extends Component {
{header}
{content}
{footer}
{resizer}
</div>
</CSSTransition>
</div>
Expand Down

0 comments on commit 7cb96d7

Please sign in to comment.