2021-11-03 19:51:16 +03:00
|
|
|
import Modifier from 'ember-modifier';
|
|
|
|
import {action} from '@ember/object';
|
2021-11-09 20:57:24 +03:00
|
|
|
import {guidFor} from '@ember/object/internals';
|
2021-11-03 19:51:16 +03:00
|
|
|
|
|
|
|
export default class MovableModifier extends Modifier {
|
2021-11-04 17:53:28 +03:00
|
|
|
moveThreshold = 3;
|
|
|
|
|
2021-11-04 13:13:56 +03:00
|
|
|
active = false;
|
2021-11-03 19:51:16 +03:00
|
|
|
currentX = undefined;
|
|
|
|
currentY = undefined;
|
|
|
|
initialX = undefined;
|
|
|
|
initialY = undefined;
|
|
|
|
xOffset = 0;
|
|
|
|
yOffset = 0;
|
|
|
|
|
2021-11-05 13:53:41 +03:00
|
|
|
// Lifecycle hooks ---------------------------------------------------------
|
|
|
|
|
2021-11-09 20:57:24 +03:00
|
|
|
didInstall() {
|
2021-11-05 13:53:41 +03:00
|
|
|
this.addStartEventListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
willDestroy() {
|
|
|
|
this.removeEventListeners();
|
2021-11-09 20:57:24 +03:00
|
|
|
this.enableSelection();
|
2021-11-05 13:53:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Custom methods -----------------------------------------------------------
|
|
|
|
|
2021-11-03 19:51:16 +03:00
|
|
|
addStartEventListeners() {
|
|
|
|
this.element.addEventListener('touchstart', this.dragStart, false);
|
|
|
|
this.element.addEventListener('mousedown', this.dragStart, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
removeStartEventListeners() {
|
|
|
|
this.element.removeEventListener('touchstart', this.dragStart, false);
|
|
|
|
this.element.removeEventListener('mousedown', this.dragStart, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
addActiveEventListeners() {
|
|
|
|
window.addEventListener('touchend', this.dragEnd, {capture: true, passive: false});
|
|
|
|
window.addEventListener('touchmove', this.drag, {capture: true, passive: false});
|
|
|
|
window.addEventListener('mouseup', this.dragEnd, {capture: true, passive: false});
|
|
|
|
window.addEventListener('mousemove', this.drag, {capture: true, passive: false});
|
|
|
|
}
|
|
|
|
|
|
|
|
removeActiveEventListeners() {
|
|
|
|
window.removeEventListener('touchend', this.dragEnd, {capture: true, passive: false});
|
|
|
|
window.removeEventListener('touchmove', this.drag, {capture: true, passive: false});
|
|
|
|
window.removeEventListener('mouseup', this.dragEnd, {capture: true, passive: false});
|
|
|
|
window.removeEventListener('mousemove', this.drag, {capture: true, passive: false});
|
2021-11-09 20:57:24 +03:00
|
|
|
window.removeEventListener('click', this.cancelClick, {capture: true, passive: false});
|
2021-11-03 19:51:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
removeEventListeners() {
|
|
|
|
this.removeStartEventListeners();
|
|
|
|
this.removeActiveEventListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
dragStart(e) {
|
2021-11-04 12:49:14 +03:00
|
|
|
if (e.type === 'touchstart' || e.button === 0) {
|
|
|
|
if (e.type === 'touchstart') {
|
|
|
|
this.initialX = e.touches[0].clientX - this.xOffset;
|
|
|
|
this.initialY = e.touches[0].clientY - this.yOffset;
|
|
|
|
} else {
|
|
|
|
this.initialX = e.clientX - this.xOffset;
|
|
|
|
this.initialY = e.clientY - this.yOffset;
|
|
|
|
}
|
2021-11-03 19:51:16 +03:00
|
|
|
|
2021-11-04 12:49:14 +03:00
|
|
|
for (const elem of (e.path || e.composedPath())) {
|
2021-11-05 13:47:39 +03:00
|
|
|
if (elem.matches('input, .ember-basic-dropdown-trigger')) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-11-04 12:49:14 +03:00
|
|
|
if (elem === this.element) {
|
|
|
|
this.addActiveEventListeners();
|
|
|
|
break;
|
|
|
|
}
|
2021-11-03 19:51:16 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
drag(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
|
2021-11-04 17:53:28 +03:00
|
|
|
let eventX, eventY;
|
2021-11-04 13:13:56 +03:00
|
|
|
|
2021-11-03 19:51:16 +03:00
|
|
|
if (e.type === 'touchmove') {
|
2021-11-04 17:53:28 +03:00
|
|
|
eventX = e.touches[0].clientX;
|
|
|
|
eventY = e.touches[0].clientY;
|
2021-11-03 19:51:16 +03:00
|
|
|
} else {
|
2021-11-04 17:53:28 +03:00
|
|
|
eventX = e.clientX;
|
|
|
|
eventY = e.clientY;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.active) {
|
|
|
|
if (
|
|
|
|
Math.abs(Math.abs(this.initialX - eventX) - Math.abs(this.xOffset)) > this.moveThreshold ||
|
|
|
|
Math.abs(Math.abs(this.initialY - eventY) - Math.abs(this.yOffset)) > this.moveThreshold
|
|
|
|
) {
|
|
|
|
this.disableScroll();
|
2021-11-09 20:57:24 +03:00
|
|
|
this.disableSelection();
|
2021-11-04 17:53:28 +03:00
|
|
|
this.disablePointerEvents();
|
|
|
|
this.active = true;
|
|
|
|
}
|
2021-11-03 19:51:16 +03:00
|
|
|
}
|
|
|
|
|
2021-11-04 17:53:28 +03:00
|
|
|
if (this.active) {
|
|
|
|
this.currentX = eventX - this.initialX;
|
|
|
|
this.currentY = eventY - this.initialY;
|
|
|
|
this.xOffset = this.currentX;
|
|
|
|
this.yOffset = this.currentY;
|
2021-11-03 19:51:16 +03:00
|
|
|
|
2021-11-04 17:53:28 +03:00
|
|
|
this.setTranslate(this.currentX, this.currentY);
|
|
|
|
}
|
2021-11-03 19:51:16 +03:00
|
|
|
}
|
|
|
|
|
2021-11-04 13:13:56 +03:00
|
|
|
@action
|
2021-11-05 13:53:41 +03:00
|
|
|
dragEnd(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
this.active = false;
|
|
|
|
|
|
|
|
this.initialX = this.currentX;
|
|
|
|
this.initialY = this.currentY;
|
|
|
|
|
|
|
|
this.removeActiveEventListeners();
|
|
|
|
this.enableScroll();
|
2021-11-09 20:57:24 +03:00
|
|
|
this.enableSelection();
|
2021-11-05 13:53:41 +03:00
|
|
|
|
|
|
|
// timeout required so immediate events blocked until the dragEnd has fully realised
|
|
|
|
setTimeout(() => {
|
|
|
|
this.enablePointerEvents();
|
|
|
|
}, 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
2021-11-04 13:13:56 +03:00
|
|
|
cancelClick(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
|
2021-11-03 19:51:16 +03:00
|
|
|
setTranslate(xPos, yPos) {
|
|
|
|
this.element.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
|
|
|
|
}
|
|
|
|
|
2021-11-04 12:49:14 +03:00
|
|
|
disableScroll() {
|
|
|
|
this.originalOverflow = this.element.style.overflow;
|
|
|
|
this.element.style.overflow = 'hidden';
|
|
|
|
}
|
|
|
|
|
|
|
|
enableScroll() {
|
|
|
|
this.element.style.overflow = this.originalOverflow;
|
|
|
|
}
|
|
|
|
|
2021-11-09 20:57:24 +03:00
|
|
|
disableSelection() {
|
|
|
|
window.getSelection().removeAllRanges();
|
|
|
|
|
|
|
|
const stylesheet = document.createElement('style');
|
|
|
|
stylesheet.id = `stylesheet-${guidFor(this)}`;
|
|
|
|
|
|
|
|
document.head.appendChild(stylesheet);
|
|
|
|
|
|
|
|
stylesheet.sheet.insertRule('* { user-select: none !important; }', 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
enableSelection() {
|
|
|
|
const stylesheet = document.getElementById(`stylesheet-${guidFor(this)}`);
|
|
|
|
stylesheet?.remove();
|
|
|
|
}
|
|
|
|
|
2021-11-04 13:13:56 +03:00
|
|
|
// disabling pointer events prevents inputs being activated when drag finishes,
|
|
|
|
// preventing clicks stops any event handlers that may otherwise result in the
|
|
|
|
// movable element being closed when the drag finishes
|
2021-11-03 19:51:16 +03:00
|
|
|
disablePointerEvents() {
|
2021-11-04 13:13:56 +03:00
|
|
|
this.element.style.pointerEvents = 'none';
|
|
|
|
window.addEventListener('click', this.cancelClick, {capture: true, passive: false});
|
2021-11-03 19:51:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
enablePointerEvents() {
|
2021-11-04 13:13:56 +03:00
|
|
|
this.element.style.pointerEvents = '';
|
|
|
|
window.removeEventListener('click', this.cancelClick, {capture: true, passive: false});
|
2021-11-03 19:51:16 +03:00
|
|
|
}
|
|
|
|
}
|