Skip to content

Commit 3fdbab6

Browse files
authored
feat: improve dnd (#300)
* feat: improve dnd * chore: cleanup * fix: bugs * chore: cleanup * feat: get closest canvas parent * chore: cleanup * feat: optimization * chore: cleanup
1 parent 536c7a0 commit 3fdbab6

32 files changed

+926
-498
lines changed

cypress/support/dnd.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,14 @@ Cypress.Commands.add(
199199
});
200200

201201
if (position === 'inside') {
202-
cy.get('@target').trigger('dragenter', {
202+
cy.get('@target').trigger('dragover', {
203203
clientX: Math.floor(x),
204204
clientY: Math.floor(y),
205205
dataTransfer,
206206
force: true,
207207
});
208208
} else {
209-
cy.get('@parent').trigger('dragenter', {
209+
cy.get('@parent').trigger('dragover', {
210210
clientX: Math.floor(x),
211211
clientY: Math.floor(y),
212212
dataTransfer,

packages/core/src/editor/query.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export function QueryMethods(state: EditorState) {
4444
return {
4545
/**
4646
* Determine the best possible location to drop the source Node relative to the target Node
47+
*
48+
* TODO: replace with Positioner.computeIndicator();
4749
*/
4850
getDropPlaceholder: (
4951
source: NodeSelector,
@@ -93,7 +95,7 @@ export function QueryMethods(state: EditorState) {
9395
...dropAction,
9496
currentNode,
9597
},
96-
error: false,
98+
error: null,
9799
};
98100

99101
const sourceNodes = getNodesFromSelector(state.nodes, source);
@@ -122,6 +124,10 @@ export function QueryMethods(state: EditorState) {
122124
return options;
123125
},
124126

127+
getNodes() {
128+
return state.nodes;
129+
},
130+
125131
/**
126132
* Helper methods to describe the specified Node
127133
* @param id

packages/core/src/editor/useInternalEditor.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import {
33
useCollectorReturnType,
44
QueryCallbacksFor,
55
wrapConnectorHooks,
6-
ChainableConnectors,
6+
EventHandlerConnectors,
77
} from '@craftjs/utils';
8-
import { useContext, useMemo } from 'react';
8+
import { useContext, useEffect, useMemo } from 'react';
99

1010
import { EditorContext } from './EditorContext';
1111
import { QueryMethods } from './query';
@@ -26,22 +26,34 @@ export type useInternalEditorReturnType<C = null> = useCollectorReturnType<
2626
> & {
2727
inContext: boolean;
2828
store: EditorStore;
29-
connectors: ChainableConnectors<
30-
CoreEventHandlers['connectors'],
31-
React.ReactElement
32-
>;
29+
connectors: EventHandlerConnectors<CoreEventHandlers, React.ReactElement>;
3330
};
3431

3532
export function useInternalEditor<C>(
3633
collector?: EditorCollector<C>
3734
): useInternalEditorReturnType<C> {
38-
const handlers = useEventHandler();
35+
const handler = useEventHandler();
3936
const store = useContext(EditorContext);
4037
const collected = useCollector(store, collector);
4138

39+
const connectorsUsage = useMemo(
40+
() => handler && handler.createConnectorsUsage(),
41+
[handler]
42+
);
43+
44+
useEffect(() => {
45+
return () => {
46+
if (!connectorsUsage) {
47+
return;
48+
}
49+
50+
connectorsUsage.cleanup();
51+
};
52+
}, [connectorsUsage]);
53+
4254
const connectors = useMemo(
43-
() => handlers && wrapConnectorHooks(handlers.connectors),
44-
[handlers]
55+
() => connectorsUsage && wrapConnectorHooks(connectorsUsage.connectors),
56+
[connectorsUsage]
4557
);
4658

4759
return {

packages/core/src/events/DefaultEventHandlers.ts

Lines changed: 84 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { isFunction } from 'lodash';
22

33
import { CoreEventHandlers, CreateHandlerOptions } from './CoreEventHandlers';
4+
import { Positioner } from './Positioner';
45
import { createShadow } from './createShadow';
56

6-
import { Indicator, NodeId, NodeTree, Node } from '../interfaces';
7-
8-
type DraggedElement = NodeId[] | NodeTree;
7+
import { Indicator, NodeId, DragTarget } from '../interfaces';
98

109
export type DefaultEventHandlersOptions = {
1110
isMultiSelectEnabled: (e: MouseEvent) => boolean;
@@ -18,10 +17,14 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
1817
DefaultEventHandlersOptions & O
1918
> {
2019
static draggedElementShadow: HTMLElement;
21-
static draggedElement: DraggedElement;
22-
static indicator: Indicator = null;
20+
dragTarget: DragTarget;
21+
positioner: Positioner | null = null;
2322
currentSelectedElementIds = [];
2423

24+
onDisable() {
25+
this.options.store.actions.clearEvents();
26+
}
27+
2528
handlers() {
2629
const store = this.options.store;
2730

@@ -108,7 +111,6 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
108111
});
109112

110113
return () => {
111-
store.actions.setNodeEvent('selected', null);
112114
unbindOnMouseDown();
113115
unbindOnClick();
114116
};
@@ -124,50 +126,41 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
124126
);
125127

126128
return () => {
127-
store.actions.setNodeEvent('hovered', null);
128129
unbindMouseover();
129130
};
130131
},
131132
drop: (el: HTMLElement, targetId: NodeId) => {
132133
const unbindDragOver = this.addCraftEventListener(
133134
el,
134135
'dragover',
135-
(e) => {
136-
e.craft.stopPropagation();
137-
e.preventDefault();
138-
}
139-
);
140-
141-
const unbindDragEnter = this.addCraftEventListener(
142-
el,
143-
'dragenter',
144136
(e) => {
145137
e.craft.stopPropagation();
146138
e.preventDefault();
147139

148-
const draggedElement = DefaultEventHandlers.draggedElement;
149-
if (!draggedElement) {
140+
if (!this.positioner) {
150141
return;
151142
}
152143

153-
let node = (draggedElement as unknown) as Node;
154-
155-
if ((draggedElement as NodeTree).rootNodeId) {
156-
const nodeTree = draggedElement as NodeTree;
157-
node = nodeTree.nodes[nodeTree.rootNodeId];
158-
}
159-
160-
const { clientX: x, clientY: y } = e;
161-
const indicator = store.query.getDropPlaceholder(node, targetId, {
162-
x,
163-
y,
164-
});
144+
const indicator = this.positioner.computeIndicator(
145+
targetId,
146+
e.clientX,
147+
e.clientY
148+
);
165149

166150
if (!indicator) {
167151
return;
168152
}
153+
169154
store.actions.setIndicator(indicator);
170-
DefaultEventHandlers.indicator = indicator;
155+
}
156+
);
157+
158+
const unbindDragEnter = this.addCraftEventListener(
159+
el,
160+
'dragenter',
161+
(e) => {
162+
e.craft.stopPropagation();
163+
e.preventDefault();
171164
}
172165
);
173166

@@ -203,18 +196,36 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
203196
query.node(id).get().dom,
204197
selectedDOMs
205198
);
206-
DefaultEventHandlers.draggedElement = selectedElementIds;
199+
this.dragTarget = {
200+
type: 'existing',
201+
nodes: selectedElementIds,
202+
};
203+
204+
this.positioner = new Positioner(
205+
this.options.store,
206+
this.dragTarget
207+
);
207208
}
208209
);
209210

210211
const unbindDragEnd = this.addCraftEventListener(el, 'dragend', (e) => {
211212
e.craft.stopPropagation();
212-
const onDropElement = (draggedElement, placement) => {
213+
214+
this.dropElement((dragTarget, indicator) => {
215+
if (dragTarget.type === 'new') {
216+
return;
217+
}
218+
213219
const index =
214-
placement.index + (placement.where === 'after' ? 1 : 0);
215-
store.actions.move(draggedElement, placement.parent.id, index);
216-
};
217-
this.dropElement(onDropElement);
220+
indicator.placement.index +
221+
(indicator.placement.where === 'after' ? 1 : 0);
222+
223+
store.actions.move(
224+
dragTarget.nodes,
225+
indicator.placement.parent.id,
226+
index
227+
);
228+
});
218229
});
219230

220231
return () => {
@@ -240,30 +251,41 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
240251
.toNodeTree();
241252

242253
const dom = e.currentTarget as HTMLElement;
243-
244254
DefaultEventHandlers.draggedElementShadow = createShadow(e, dom, [
245-
dom,
255+
el,
246256
]);
247-
DefaultEventHandlers.draggedElement = tree;
257+
this.dragTarget = {
258+
type: 'new',
259+
tree,
260+
};
261+
262+
this.positioner = new Positioner(
263+
this.options.store,
264+
this.dragTarget
265+
);
248266
}
249267
);
250268

251269
const unbindDragEnd = this.addCraftEventListener(el, 'dragend', (e) => {
252270
e.craft.stopPropagation();
253-
const onDropElement = (draggedElement, placement) => {
271+
this.dropElement((dragTarget, indicator) => {
272+
if (dragTarget.type === 'existing') {
273+
return;
274+
}
275+
254276
const index =
255-
placement.index + (placement.where === 'after' ? 1 : 0);
277+
indicator.placement.index +
278+
(indicator.placement.where === 'after' ? 1 : 0);
256279
store.actions.addNodeTree(
257-
draggedElement,
258-
placement.parent.id,
280+
dragTarget.tree,
281+
indicator.placement.parent.id,
259282
index
260283
);
261284

262285
if (options && isFunction(options.onCreate)) {
263-
options.onCreate(draggedElement);
286+
options.onCreate(dragTarget.tree);
264287
}
265-
};
266-
this.dropElement(onDropElement);
288+
});
267289
});
268290

269291
return () => {
@@ -276,32 +298,33 @@ export class DefaultEventHandlers<O = {}> extends CoreEventHandlers<
276298
}
277299

278300
private dropElement(
279-
onDropNode: (
280-
draggedElement: DraggedElement,
281-
placement: Indicator['placement']
282-
) => void
301+
onDropNode: (dragTarget: DragTarget, placement: Indicator) => void
283302
) {
284303
const store = this.options.store;
285304

286-
const {
287-
draggedElement,
288-
draggedElementShadow,
289-
indicator,
290-
} = DefaultEventHandlers;
291-
if (draggedElement && indicator && !indicator.error) {
292-
const { placement } = indicator;
293-
onDropNode(draggedElement, placement);
305+
if (!this.positioner) {
306+
return;
307+
}
308+
309+
const { draggedElementShadow } = DefaultEventHandlers;
310+
311+
const indicator = this.positioner.getIndicator();
312+
313+
if (this.dragTarget && indicator && !indicator.error) {
314+
onDropNode(this.dragTarget, indicator);
294315
}
295316

296317
if (draggedElementShadow) {
297318
draggedElementShadow.parentNode.removeChild(draggedElementShadow);
298319
DefaultEventHandlers.draggedElementShadow = null;
299320
}
300321

301-
DefaultEventHandlers.draggedElement = null;
302-
DefaultEventHandlers.indicator = null;
322+
this.dragTarget = null;
303323

304324
store.actions.setIndicator(null);
305325
store.actions.setNodeEvent('dragged', null);
326+
this.positioner.cleanup();
327+
328+
this.positioner = null;
306329
}
307330
}

0 commit comments

Comments
 (0)