diff --git a/CHANGELOG.md b/CHANGELOG.md index e7008cb2..58f8818f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to [`@bpmn-io/properties-panel`](https://github.com/bpmn-io/ ___Note:__ Yet to be released changes appear here._ * `FEAT`: improve FEEL popup lifecycle events ([#294](https://github.com/bpmn-io/properties-panel/pull/294)) +* `FEAT`: add drag trap to popup component ([#289](https://github.com/bpmn-io/properties-panel/issues/289)) +* `FEAT`: allow listen to `feelPopup.dragstart`, `feelPopup.dragover` and `feelPopup.dragend` events ([#299](https://github.com/bpmn-io/properties-panel/pull/292)) ## 3.7.1 diff --git a/src/assets/properties-panel.css b/src/assets/properties-panel.css index 774430a8..d249db5a 100644 --- a/src/assets/properties-panel.css +++ b/src/assets/properties-panel.css @@ -1237,7 +1237,7 @@ textarea.bio-properties-panel-input { --feel-popup-close-background-color: hsla(219, 99%, 53%, 1); --feel-popup-gutters-background-color: hsla(0, 0%, 90%, 1); - position: absolute; + position: fixed; display: flex; flex: auto; flex-direction: column; diff --git a/src/components/Popup.js b/src/components/Popup.js index 8daea783..bcbeb120 100644 --- a/src/components/Popup.js +++ b/src/components/Popup.js @@ -4,8 +4,6 @@ import { useEffect, useRef } from 'preact/hooks'; import classNames from 'classnames'; -import { throttle } from 'min-dash'; - import * as focusTrap from 'focus-trap'; import { DragIcon } from './icons'; @@ -141,6 +139,7 @@ function Title(props) { children, className, draggable, + emit = () => {}, title, ...rest } = props; @@ -156,7 +155,9 @@ function Title(props) { const titleRef = useRef(); - const onMove = throttle((_, delta) => { + const onMove = (event, delta) => { + cancel(event); + const { x: dx, y: dy } = delta; const newPosition = { @@ -168,7 +169,10 @@ function Title(props) { popupParent.style.top = newPosition.y + 'px'; popupParent.style.left = newPosition.x + 'px'; - }); + + // notify interested parties + emit('dragover', { newPosition, delta }); + }; const onMoveStart = (event) => { @@ -176,6 +180,8 @@ function Title(props) { const onDragStart = createDragger(onMove, dragPreviewRef.current); onDragStart(event); + event.stopPropagation(); + const popupParent = getPopupParent(titleRef.current); const bounds = popupParent.getBoundingClientRect(); @@ -183,10 +189,16 @@ function Title(props) { x: bounds.left, y: bounds.top }; + + // notify interested parties + emit('dragstart'); }; const onMoveEnd = () => { context.current.newPosition = null; + + // notify interested parties + emit('dragend'); }; return ( @@ -248,4 +260,9 @@ function Footer(props) { function getPopupParent(node) { return node.closest('.bio-properties-panel-popup'); +} + +function cancel(event) { + event.preventDefault(); + event.stopPropagation(); } \ No newline at end of file diff --git a/src/components/entries/FEEL/FeelPopup.js b/src/components/entries/FEEL/FeelPopup.js index 8101b782..cbd4eba8 100644 --- a/src/components/entries/FEEL/FeelPopup.js +++ b/src/components/entries/FEEL/FeelPopup.js @@ -183,6 +183,7 @@ function FeelPopupComponent(props) {
', function() { describe('events', function() { + function expectDraggingEvent(event) { + + it('should listen on <' + event + '>', async function() { + + // given + const eventBus = new EventBus(); + + const spy = sinon.spy(); + + eventBus.on(event, spy); + + createFeelPopup({ popupContainer: container, eventBus }, container); + + // assume + expect(getFeelEditor(container)).to.not.exist; + + // when + await act(() => { + eventBus.fire('feelPopup._open'); + }); + + const header = domQuery('.bio-properties-panel-popup__header', container); + const dragger = domQuery('.bio-properties-panel-popup__drag-handle', header); + const draggerBounds = dragger.getBoundingClientRect(); + + // when + startDragging(dragger); + moveDragging(dragger, { clientX: draggerBounds.x + 20, clientY: draggerBounds.y }); + endDragging(dragger); + + // then + expect(spy).to.have.been.calledOnce; + }); + } + + it('should listen on ', async function() { // given @@ -255,6 +291,15 @@ describe('', function() { }); + expectDraggingEvent('feelPopup.dragstart'); + + + expectDraggingEvent('feelPopup.dragover'); + + + expectDraggingEvent('feelPopup.dragend'); + + it('', async function() { // given @@ -703,4 +748,42 @@ function getFeelEditor(container) { function getFeelersEditor(container) { return domQuery('.bio-properties-panel-feelers-editor-container', container); +} + +function dispatchEvent(element, type, options = {}) { + const event = document.createEvent('Event'); + + event.initEvent(type, true, true); + + Object.keys(options).forEach(key => event[ key ] = options[ key ]); + + element.dispatchEvent(event); +} + +function startDragging(node, position) { + if (!position) { + const bounds = node.getBoundingClientRect(); + position = { + clientX: bounds.x, + clientY: bounds.y + }; + } + + dispatchEvent(node, 'dragstart', position); +} + +function moveDragging(node, position) { + if (!position) { + const bounds = node.getBoundingClientRect(); + position = { + clientX: bounds.x + 20, + clientY: bounds.y + 20 + }; + } + + dispatchEvent(node, 'dragover', position); +} + +function endDragging(node) { + dispatchEvent(node, 'dragend'); } \ No newline at end of file diff --git a/test/spec/components/Popup.spec.js b/test/spec/components/Popup.spec.js index 6dba0731..71ca3ef6 100644 --- a/test/spec/components/Popup.spec.js +++ b/test/spec/components/Popup.spec.js @@ -298,6 +298,42 @@ describe('', function() { expect(newBounds.x).to.eql(oldBounds.x + 20); }); + + it('should not bubble dragging events to parent', function() { + + // given + const dragStartSpy = sinon.spy(); + const dragOverSpy = sinon.spy(); + const dragEnterSpy = sinon.spy(); + + container.addEventListener('dragstart', dragStartSpy); + container.addEventListener('dragover', dragOverSpy); + container.addEventListener('dragenter', dragEnterSpy); + + render( + + + , + { container } + ); + + const header = domQuery('.bio-properties-panel-popup__header', container); + const dragger = domQuery('.bio-properties-panel-popup__drag-handle', header); + + const draggerBounds = dragger.getBoundingClientRect(); + + // when + startDragging(dragger); + moveDragging(dragger, { clientX: draggerBounds.x + 20, clientY: draggerBounds.y }); + endDragging(dragger); + + // then + expect(dragStartSpy).to.not.have.been.called; + expect(dragOverSpy).to.not.have.been.called; + expect(dragEnterSpy).to.not.have.been.called; + }); + + }); });