Skip to content

Commit

Permalink
Propagate scroll from backdrop (#652)
Browse files Browse the repository at this point in the history
* Propagate scrolling from backdrop to scroll target

* Add a test case for scroll propagation

* Ensure the scroll works through longer ranges

* Fix a typo in a private function
  • Loading branch information
krassowski authored Oct 30, 2023
1 parent 42b30f7 commit f4e065b
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 17 deletions.
105 changes: 88 additions & 17 deletions packages/dragdrop/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ export class Drag implements IDisposable {
let prevElem = this._currentElement;

// Find the current indicated element at the given position.
let currElem = Private.findElementBehidBackdrop(event, this.document);
let currElem = Private.findElementBehindBackdrop(event, this.document);

// Update the current element reference.
this._currentElement = currElem;
Expand Down Expand Up @@ -837,30 +837,51 @@ namespace Private {
}

/**
* Find the event target using pointer position.
* Find the event target using pointer position if given, or otherwise
* the central position of the backdrop.
*/
export function findElementBehidBackdrop(
event: PointerEvent,
export function findElementBehindBackdrop(
event?: PointerEvent,
root: Document | ShadowRoot = document
) {
// Check if we already cached element for this event.
if (lastElementSearch && event == lastElementSearch.event) {
return lastElementSearch.element;
if (event) {
// Check if we already cached element for this event.
if (lastElementEventSearch && event == lastElementEventSearch.event) {
return lastElementEventSearch.element;
}
Private.cursorBackdrop.style.zIndex = '-1000';
const element: Element | null = root.elementFromPoint(
event.clientX,
event.clientY
);
Private.cursorBackdrop.style.zIndex = '';
lastElementEventSearch = { event, element };
return element;
} else {
const transform = cursorBackdrop.style.transform;
if (lastElementSearch && transform === lastElementSearch.transform) {
return lastElementSearch.element;
}
const bbox = Private.cursorBackdrop.getBoundingClientRect();
Private.cursorBackdrop.style.zIndex = '-1000';
const element = root.elementFromPoint(
bbox.left + bbox.width / 2,
bbox.top + bbox.height / 2
);
Private.cursorBackdrop.style.zIndex = '';
lastElementSearch = { transform, element };
return element;
}
Private.cursorBackdrop.style.zIndex = '-1000';
const element: Element | null = root.elementFromPoint(
event.clientX,
event.clientY
);
Private.cursorBackdrop.style.zIndex = '';
lastElementSearch = { event, element };
return element;
}

let lastElementSearch: {
let lastElementEventSearch: {
event: PointerEvent;
element: Element | null;
} | null = null;
let lastElementSearch: {
transform: string;
element: Element | null;
} | null = null;

/**
* Find the drag scroll target under the mouse, if any.
Expand All @@ -871,7 +892,7 @@ namespace Private {
let y = event.clientY;

// Get the element under the mouse.
let element: Element | null = findElementBehidBackdrop(event);
let element: Element | null = findElementBehindBackdrop(event);

// Search for a scrollable target based on the mouse position.
// The null assert in third clause of for-loop is required due to:
Expand Down Expand Up @@ -1240,15 +1261,25 @@ namespace Private {
// native double click detection, used in e.g. datagrid editing.
cursorBackdrop.style.transform = 'scale(0)';
body.appendChild(cursorBackdrop);
resetBackdropScroll();
document.addEventListener('pointermove', alignBackdrop, {
capture: true,
passive: true
});
cursorBackdrop.addEventListener('scroll', propagateBackdropScroll, {
capture: true,
passive: true
});
}
cursorBackdrop.style.cursor = cursor;
return new DisposableDelegate(() => {
if (id === overrideCursorID && cursorBackdrop.isConnected) {
document.removeEventListener('pointermove', alignBackdrop, true);
cursorBackdrop.removeEventListener(
'scroll',
propagateBackdropScroll,
true
);
body.removeChild(cursorBackdrop);
}
});
Expand All @@ -1264,6 +1295,46 @@ namespace Private {
cursorBackdrop.style.transform = `translate(${event.clientX}px, ${event.clientY}px)`;
}

/**
* Propagate the scroll event from the backdrop element to the scroll target.
* The scroll target is defined by presence of `data-lm-dragscroll` attribute.
*/
function propagateBackdropScroll(_event: Event) {
if (!cursorBackdrop) {
return;
}
// Get the element under behind the centre of the cursor backdrop
// (essentially behind the cursor, but possibly a few pixels off).
let element: Element | null = findElementBehindBackdrop();
if (!element) {
return;
}
// Find scroll target.
const scrollTarget = element.closest('[data-lm-dragscroll]');
if (!scrollTarget) {
return;
}
// Apply the scroll delta to the correct target.
scrollTarget.scrollTop += cursorBackdrop.scrollTop - backdropScrollOrigin;
scrollTarget.scrollLeft += cursorBackdrop.scrollLeft - backdropScrollOrigin;

// Center the scroll position.
resetBackdropScroll();
}

/**
* Reset the backdrop scroll to allow further scrolling.
*/
function resetBackdropScroll() {
cursorBackdrop.scrollTop = backdropScrollOrigin;
cursorBackdrop.scrollLeft = backdropScrollOrigin;
}

/**
* The center of the backdrop node scroll area.
*/
const backdropScrollOrigin = 500;

/**
* Create cursor backdrop node.
*/
Expand Down
16 changes: 16 additions & 0 deletions packages/dragdrop/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,29 @@
|----------------------------------------------------------------------------*/

.lm-cursor-backdrop {
top: 0px;
left: 0px;
position: fixed;
width: 200px;
height: 200px;
margin-top: -100px;
margin-left: -100px;
will-change: transform;
z-index: 100;
scrollbar-width: none;
-ms-overflow-style: none;
overflow: scroll;
}

.lm-cursor-backdrop::after {
content: '';
height: 1200px;
width: 1200px;
display: block;
}

.lm-cursor-backdrop::-webkit-scrollbar {
display: none;
}

.lm-mod-drag-image {
Expand Down
32 changes: 32 additions & 0 deletions packages/dragdrop/tests/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,38 @@ describe('@lumino/dragdrop', () => {
expect(backdrop.style.transform).to.equal('translate(100px, 500px)');
override.dispose();
});

it('should propagate scroll to underlying target', () => {
let override = Drag.overrideCursor('wait');
const backdrop = document.querySelector(
'.lm-cursor-backdrop'
) as HTMLElement;

const wrapper = document.createElement('div');
const content = document.createElement('div');
document.elementFromPoint = (_x, _y) => {
return wrapper;
};
document.body.appendChild(wrapper);
wrapper.appendChild(content);
wrapper.setAttribute('data-lm-dragscroll', 'true');
wrapper.style.overflow = 'scroll';
wrapper.style.height = '100px';
wrapper.style.width = '100px';
content.style.height = '2000px';
content.style.width = '2000px';

backdrop.scrollTop += 400;
backdrop.dispatchEvent(new Event('scroll'));
expect(wrapper.scrollTop).to.equal(400);

backdrop.scrollTop += 400;
backdrop.dispatchEvent(new Event('scroll'));
expect(wrapper.scrollTop).to.equal(800);

override.dispose();
document.body.removeChild(wrapper);
});
});
});
});

0 comments on commit f4e065b

Please sign in to comment.