odoo.define('point_of_sale.Draggable', function(require) {
'use strict';
const { useExternalListener } = owl.hooks;
const { useListener } = require('web.custom_hooks');
const PosComponent = require('point_of_sale.PosComponent');
const Registries = require('point_of_sale.Registries');
/**
* Wrap an element or a component with { position: absolute } to make it
* draggable around the limitArea or the nearest positioned ancestor.
*
* e.g.
* ```
*
* ```
*
* In the above snippet, if the popup div is { position: absolute },
* then it becomes draggable around the .limit-area element if it is dragged
* thru its Header -- because of the .drag-handle element.
*
* @trigger 'drag-end' when dragging ended with payload `{ loc: { top, left } }`
*/
class Draggable extends PosComponent {
constructor() {
super(...arguments);
this.isDragging = false;
this.dx = 0;
this.dy = 0;
// drag with mouse
useExternalListener(document, 'mousemove', this.move);
useExternalListener(document, 'mouseup', this.endDrag);
// drag with touch
useExternalListener(document, 'touchmove', this.move);
useExternalListener(document, 'touchend', this.endDrag);
useListener('mousedown', '.drag-handle', this.startDrag);
useListener('touchstart', '.drag-handle', this.startDrag);
}
mounted() {
this.limitArea = this.props.limitArea
? document.querySelector(this.props.limitArea)
: this.el.offsetParent;
this.limitAreaBoundingRect = this.limitArea.getBoundingClientRect();
if (this.limitArea === this.el.offsetParent) {
this.limitLeft = 0;
this.limitTop = 0;
this.limitRight = this.limitAreaBoundingRect.width;
this.limitBottom = this.limitAreaBoundingRect.height;
} else {
this.limitLeft = -this.el.offsetParent.offsetLeft;
this.limitTop = -this.el.offsetParent.offsetTop;
this.limitRight =
this.limitAreaBoundingRect.width - this.el.offsetParent.offsetLeft;
this.limitBottom =
this.limitAreaBoundingRect.height - this.el.offsetParent.offsetTop;
}
this.limitAreaWidth = this.limitAreaBoundingRect.width;
this.limitAreaHeight = this.limitAreaBoundingRect.height;
// absolutely position the element then remove the transform.
const elBoundingRect = this.el.getBoundingClientRect();
this.el.style.top = `${elBoundingRect.top}px`;
this.el.style.left = `${elBoundingRect.left}px`;
this.el.style.transform = 'none';
}
startDrag(event) {
let realEvent;
if (event instanceof CustomEvent) {
realEvent = event.detail;
} else {
realEvent = event;
}
const { x, y } = this._getEventLoc(realEvent);
this.isDragging = true;
this.dx = this.el.offsetLeft - x;
this.dy = this.el.offsetTop - y;
event.stopPropagation();
}
move(event) {
if (this.isDragging) {
const { x: pointerX, y: pointerY } = this._getEventLoc(event);
const posLeft = this._getPosLeft(pointerX, this.dx);
const posTop = this._getPosTop(pointerY, this.dy);
this.el.style.left = `${posLeft}px`;
this.el.style.top = `${posTop}px`;
}
}
endDrag() {
if (this.isDragging) {
this.isDragging = false;
this.trigger('drag-end', {
loc: { top: this.el.offsetTop, left: this.el.offsetLeft },
});
}
}
_getEventLoc(event) {
let coordX, coordY;
if (event.touches && event.touches[0]) {
coordX = event.touches[0].clientX;
coordY = event.touches[0].clientY;
} else {
coordX = event.clientX;
coordY = event.clientY;
}
return {
x: coordX,
y: coordY,
};
}
_getPosLeft(pointerX, dx) {
const posLeft = pointerX + dx;
if (posLeft < this.limitLeft) {
return this.limitLeft;
} else if (posLeft > this.limitRight - this.el.offsetWidth) {
return this.limitRight - this.el.offsetWidth;
}
return posLeft;
}
_getPosTop(pointerY, dy) {
const posTop = pointerY + dy;
if (posTop < this.limitTop) {
return this.limitTop;
} else if (posTop > this.limitBottom - this.el.offsetHeight) {
return this.limitBottom - this.el.offsetHeight;
}
return posTop;
}
}
Draggable.template = 'Draggable';
Registries.Component.add(Draggable);
return Draggable;
});