Skip to content

Commit dc047c9

Browse files
authored
fix: drag shadow (#304)
* feat: improve dnd * chore: cleanup * fix: bugs * chore: cleanup * fix: initial canvas dnd * fix: drag shadow * chore: cleanup * feat: get closest canvas parent * chore: cleanup * feat: optimization * chore: comment
1 parent 3fdbab6 commit dc047c9

File tree

4 files changed

+57
-23
lines changed

4 files changed

+57
-23
lines changed

packages/core/src/events/DefaultEventHandlers.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isChromium, isLinux } from '@craftjs/utils';
12
import { isFunction } from 'lodash';
23

34
import { CoreEventHandlers, CreateHandlerOptions } from './CoreEventHandlers';
@@ -16,7 +17,14 @@ export type DefaultEventHandlersOptions = {
1617
export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
1718
DefaultEventHandlersOptions & O
1819
> {
19-
static draggedElementShadow: HTMLElement;
20+
/**
21+
* Note: Multiple drag shadows (ie: via multiselect in v0.2 and higher) do not look good on Linux Chromium due to way it renders drag shadows in general,
22+
* so will have to fallback to the single shadow approach above for the time being
23+
* see: https://bugs.chromium.org/p/chromium/issues/detail?id=550999
24+
*/
25+
static forceSingleDragShadow = isChromium() && isLinux();
26+
27+
draggedElementShadow: HTMLElement;
2028
dragTarget: DragTarget;
2129
positioner: Positioner | null = null;
2230
currentSelectedElementIds = [];
@@ -191,11 +199,12 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
191199
(id) => query.node(id).get().dom
192200
);
193201

194-
DefaultEventHandlers.draggedElementShadow = createShadow(
202+
this.draggedElementShadow = createShadow(
195203
e,
196-
query.node(id).get().dom,
197-
selectedDOMs
204+
selectedDOMs,
205+
DefaultEventHandlers.forceSingleDragShadow
198206
);
207+
199208
this.dragTarget = {
200209
type: 'existing',
201210
nodes: selectedElementIds,
@@ -251,9 +260,11 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
251260
.toNodeTree();
252261

253262
const dom = e.currentTarget as HTMLElement;
254-
DefaultEventHandlers.draggedElementShadow = createShadow(e, dom, [
255-
el,
256-
]);
263+
this.draggedElementShadow = createShadow(
264+
e,
265+
[dom],
266+
DefaultEventHandlers.forceSingleDragShadow
267+
);
257268
this.dragTarget = {
258269
type: 'new',
259270
tree,
@@ -306,7 +317,7 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
306317
return;
307318
}
308319

309-
const { draggedElementShadow } = DefaultEventHandlers;
320+
const draggedElementShadow = this.draggedElementShadow;
310321

311322
const indicator = this.positioner.getIndicator();
312323

@@ -316,7 +327,7 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
316327

317328
if (draggedElementShadow) {
318329
draggedElementShadow.parentNode.removeChild(draggedElementShadow);
319-
DefaultEventHandlers.draggedElementShadow = null;
330+
this.draggedElementShadow = null;
320331
}
321332

322333
this.dragTarget = null;
Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
1+
// TODO: this approach does not work with Safari
2+
// Works partially with Linux (except on Chrome)
3+
// We'll need an alternate way to create drag shadows
14
export const createShadow = (
25
e: DragEvent,
3-
targetDOM: HTMLElement,
4-
shadowsToCreate: HTMLElement[]
6+
shadowsToCreate: HTMLElement[],
7+
forceSingleShadow: boolean = false
58
) => {
6-
const selectorDOM = e.currentTarget as HTMLElement;
7-
const {
8-
top: targetTop,
9-
left: targetLeft,
10-
} = targetDOM.getBoundingClientRect();
11-
12-
if (!selectorDOM) {
13-
return;
9+
if (shadowsToCreate.length === 1 || forceSingleShadow) {
10+
const { width, height } = shadowsToCreate[0].getBoundingClientRect();
11+
const shadow = shadowsToCreate[0].cloneNode(true) as HTMLElement;
12+
13+
shadow.style.position = `fixed`;
14+
shadow.style.left = `-100%`;
15+
shadow.style.top = `-100%`;
16+
shadow.style.width = `${width}px`;
17+
shadow.style.height = `${height}px`;
18+
shadow.style.pointerEvents = 'none';
19+
20+
document.body.appendChild(shadow);
21+
e.dataTransfer.setDragImage(shadow, 0, 0);
22+
23+
return shadow;
1424
}
1525

26+
/**
27+
* If there's supposed to be multiple drag shadows, we will create a single container div to store them
28+
* That container will be used as the drag shadow for the current drag event
29+
*/
1630
const container = document.createElement('div');
1731
container.style.position = 'fixed';
1832
container.style.left = '-100%';
@@ -22,19 +36,20 @@ export const createShadow = (
2236
container.style.pointerEvents = 'none';
2337

2438
shadowsToCreate.forEach((dom) => {
25-
const shadow = dom.cloneNode(true) as HTMLElement;
2639
const { width, height, top, left } = dom.getBoundingClientRect();
40+
const shadow = dom.cloneNode(true) as HTMLElement;
41+
2742
shadow.style.position = `absolute`;
28-
shadow.style.width = `${width}px`;
29-
shadow.style.height = `${height}px`;
3043
shadow.style.left = `${left}px`;
3144
shadow.style.top = `${top}px`;
45+
shadow.style.width = `${width}px`;
46+
shadow.style.height = `${height}px`;
3247

3348
container.appendChild(shadow);
3449
});
3550

3651
document.body.appendChild(container);
37-
e.dataTransfer.setDragImage(container, targetLeft, targetTop);
52+
e.dataTransfer.setDragImage(container, e.clientX, e.clientY);
3853

3954
return container;
4055
};

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './deprecate';
99
export * from './utilityTypes';
1010
export * from './History';
1111
export * from './getRandomId';
12+
export * from './platform';

packages/utils/src/platform.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const isClientSide = () => typeof window !== 'undefined';
2+
3+
export const isLinux = () =>
4+
isClientSide() && /Linux/i.test(window.navigator.userAgent);
5+
6+
export const isChromium = () =>
7+
isClientSide() && /Chrome/i.test(window.navigator.userAgent);

0 commit comments

Comments
 (0)