From 7d147b06e3c2dce22fba7bda2d88f22ce30bcf52 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 10 May 2024 15:12:47 +0200 Subject: [PATCH] feat: drag plugin modal --- .../src/lib/drag-handler.spec.ts | 79 +++++++++++++++++++ libs/plugins-runtime/src/lib/drag-handler.ts | 32 ++++++++ libs/plugins-runtime/src/lib/plugin-modal.ts | 8 ++ 3 files changed, 119 insertions(+) create mode 100644 libs/plugins-runtime/src/lib/drag-handler.spec.ts create mode 100644 libs/plugins-runtime/src/lib/drag-handler.ts diff --git a/libs/plugins-runtime/src/lib/drag-handler.spec.ts b/libs/plugins-runtime/src/lib/drag-handler.spec.ts new file mode 100644 index 0000000..6684179 --- /dev/null +++ b/libs/plugins-runtime/src/lib/drag-handler.spec.ts @@ -0,0 +1,79 @@ +import { expect, describe, vi } from 'vitest'; +import { dragHandler } from './drag-handler.js'; + +describe('dragHandler', () => { + let element: HTMLElement; + + beforeEach(() => { + element = document.createElement('div'); + document.body.appendChild(element); + }); + + afterEach(() => { + document.body.removeChild(element); + vi.clearAllMocks(); + }); + + it('should attach mousedown event listener to the element', () => { + const addEventListenerMock = vi.spyOn(element, 'addEventListener'); + + dragHandler(element); + + expect(addEventListenerMock).toHaveBeenCalledWith( + 'mousedown', + expect.any(Function) + ); + }); + + it('should update element transform on mousemove', () => { + const mouseDownEvent = new MouseEvent('mousedown', { + clientX: 100, + clientY: 100, + }); + + dragHandler(element); + + element.dispatchEvent(mouseDownEvent); + + const mouseMoveEvent = new MouseEvent('mousemove', { + clientX: 150, + clientY: 150, + }); + document.dispatchEvent(mouseMoveEvent); + + expect(element.style.transform).toBe('translate(50px, 50px)'); + + const mouseMoveEvent2 = new MouseEvent('mousemove', { + clientX: 200, + clientY: 200, + }); + document.dispatchEvent(mouseMoveEvent2); + + expect(element.style.transform).toBe('translate(100px, 100px)'); + }); + + it('should remove event listeners on mouseup', () => { + const removeEventListenerMock = vi.spyOn(document, 'removeEventListener'); + + const mouseDownEvent = new MouseEvent('mousedown', { + clientX: 100, + clientY: 100, + }); + + dragHandler(element); + + element.dispatchEvent(mouseDownEvent); + + const mouseUpEvent = new MouseEvent('mouseup'); + document.dispatchEvent(mouseUpEvent); + + expect(removeEventListenerMock).toHaveBeenCalledWith( + 'mousemove', + expect.any(Function) + ); + expect(removeEventListenerMock).toHaveBeenCalledWith( + 'mouseup', + expect.any(Function) + ); + }); +}); diff --git a/libs/plugins-runtime/src/lib/drag-handler.ts b/libs/plugins-runtime/src/lib/drag-handler.ts new file mode 100644 index 0000000..b2d4993 --- /dev/null +++ b/libs/plugins-runtime/src/lib/drag-handler.ts @@ -0,0 +1,32 @@ +export const dragHandler = (el: HTMLElement) => { + let currentTranslate = { x: 0, y: 0 }; + let initialTranslate = { x: 0, y: 0 }; + let initialClientPosition = { x: 0, y: 0 }; + + const handleMouseMove = (moveEvent: MouseEvent) => { + const { clientX: moveX, clientY: moveY } = moveEvent; + const deltaX = moveX - initialClientPosition.x + initialTranslate.x; + const deltaY = moveY - initialClientPosition.y + initialTranslate.y; + + currentTranslate = { x: deltaX, y: deltaY }; + + el.style.transform = `translate(${deltaX}px, ${deltaY}px)`; + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + const handleMouseDown = (e: MouseEvent) => { + initialClientPosition = { x: e.clientX, y: e.clientY }; + initialTranslate = { x: currentTranslate.x, y: currentTranslate.y }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + el.addEventListener('mousedown', handleMouseDown); + + return handleMouseUp; +}; diff --git a/libs/plugins-runtime/src/lib/plugin-modal.ts b/libs/plugins-runtime/src/lib/plugin-modal.ts index 17e4a3f..b477635 100644 --- a/libs/plugins-runtime/src/lib/plugin-modal.ts +++ b/libs/plugins-runtime/src/lib/plugin-modal.ts @@ -2,6 +2,7 @@ const closeSvg = ` `; import type { PenpotTheme } from '@penpot/plugin-types'; +import { dragHandler } from './drag-handler.js'; export class PluginModalElement extends HTMLElement { constructor() { @@ -10,6 +11,7 @@ export class PluginModalElement extends HTMLElement { } #wrapper: HTMLElement | null = null; + #dragEvents: ReturnType | null = null; setTheme(theme: PenpotTheme) { if (this.#wrapper) { @@ -17,6 +19,10 @@ export class PluginModalElement extends HTMLElement { } } + disconnectedCallback() { + this.#dragEvents?.(); + } + connectedCallback() { const title = this.getAttribute('title'); const iframeSrc = this.getAttribute('iframe-src'); @@ -33,6 +39,7 @@ export class PluginModalElement extends HTMLElement { this.#wrapper = document.createElement('div'); this.#wrapper.classList.add('wrapper'); + this.#dragEvents = dragHandler(this.#wrapper); const header = document.createElement('div'); header.classList.add('header'); @@ -149,6 +156,7 @@ export class PluginModalElement extends HTMLElement { font-weight: var(--font-weight-bold); margin: 0; margin-inline-end: var(--spacing-4); + user-select: none; } iframe {