Skip to content

Commit d6a9be9

Browse files
committed
feat: Add shouldCopyOnOutsideDrop prop to enable copying of nodes that leave the tree
When enabled, `shouldCopyOnOutsideDrop` makes nodes dragged out of the tree leave behind a copy in their place.
1 parent cbca55b commit d6a9be9

File tree

3 files changed

+65
-14
lines changed

3 files changed

+65
-14
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ onMoveNode | func | | | Ca
5757
onVisibilityToggle | func | | | Called after children nodes collapsed or expanded. <div>`({ treeData: object[], node: object, expanded: bool }): void`</div>
5858
canDrag | func or bool | `true` | | Return false from callback to prevent node from dragging, by hiding the drag handle. Set prop to `false` to disable dragging on all nodes. <div>`({ node: object, path: number[] or string[], treeIndex: number, lowerSiblingCounts: number[], isSearchMatch: bool, isSearchFocus: bool }): bool`</div>
5959
canDrop | func | | | Return false to prevent node from dropping in the given location. <div>`({ node: object, prevPath: number[] or string[], prevParent: object, prevTreeIndex: number, nextPath: number[] or string[], nextParent: object, nextTreeIndex: number}): bool`</div>
60+
canDrag | func or bool | `true` | | Return false from callback to prevent node from dragging, by hiding the drag handle. Set prop to `false` to disable dragging on all nodes. <div>`({ node: object, path: number[] or string[], treeIndex: number, lowerSiblingCounts: number[], isSearchMatch: bool, isSearchFocus: bool }): bool`</div>
6061
reactVirtualizedListProps | object | | | Custom properties to hand to the [react-virtualized list](https://github.com/bvaughn/react-virtualized/blob/master/docs/List.md#prop-types)
6162
rowHeight | number or func | `62` | | Used by react-virtualized. Either a fixed row height (number) or a function that returns the height of a row given its index: `({ index: number }): number`
6263
slideRegionSize | number | `100` | | Size in px of the region near the edges that initiates scrolling on dragover.

examples/storybooks/tree-to-tree.js

+19
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ class App extends Component {
1111
{ title: 'node2' },
1212
],
1313
treeData2: [{ title: 'node3' }, { title: 'node4' }],
14+
shouldCopyOnOutsideDrop: false,
1415
};
1516
}
1617

1718
render() {
1819
// Both trees need to share this same node type in their
1920
// `dndType` prop
2021
const externalNodeType = 'yourNodeType';
22+
const { shouldCopyOnOutsideDrop } = this.state;
2123
return (
2224
<div>
2325
<div
@@ -32,6 +34,7 @@ class App extends Component {
3234
treeData={this.state.treeData1}
3335
onChange={treeData1 => this.setState({ treeData1 })}
3436
dndType={externalNodeType}
37+
shouldCopyOnOutsideDrop={shouldCopyOnOutsideDrop}
3538
/>
3639
</div>
3740

@@ -47,10 +50,26 @@ class App extends Component {
4750
treeData={this.state.treeData2}
4851
onChange={treeData2 => this.setState({ treeData2 })}
4952
dndType={externalNodeType}
53+
shouldCopyOnOutsideDrop={shouldCopyOnOutsideDrop}
5054
/>
5155
</div>
5256

5357
<div style={{ clear: 'both' }} />
58+
59+
<div>
60+
<label htmlFor="should-copy" style={{ fontSize: '0.8rem' }}>
61+
Enable node copy via <b>shouldCopyOnOutsideDrop</b>:
62+
<input
63+
type="checkbox"
64+
id="should-copy"
65+
value={shouldCopyOnOutsideDrop}
66+
onChange={event =>
67+
this.setState({
68+
shouldCopyOnOutsideDrop: event.target.checked,
69+
})}
70+
/>
71+
</label>
72+
</div>
5473
</div>
5574
);
5675
}

src/react-sortable-tree.js

+45-14
Original file line numberDiff line numberDiff line change
@@ -311,32 +311,55 @@ class ReactSortableTree extends Component {
311311
}
312312

313313
endDrag(dropResult) {
314-
// Drop was cancelled
315-
if (!dropResult) {
314+
const resetTree = () =>
316315
this.setState({
317316
draggingTreeData: null,
318317
swapFrom: null,
319318
swapLength: null,
320319
swapDepth: null,
321320
rows: this.getRows(this.props.treeData),
322321
});
322+
323+
// Drop was cancelled
324+
if (!dropResult) {
325+
resetTree();
323326
} else if (dropResult.treeId !== this.treeId) {
324327
// The node was dropped in an external drop target or tree
325-
const { node, path: prevPath, treeIndex: prevTreeIndex } = dropResult;
326-
const treeData = this.state.draggingTreeData || this.props.treeData;
327-
this.props.onChange(treeData);
328-
329-
this.props.onMoveNode({
330-
treeData,
328+
const { node, path, treeIndex } = dropResult;
329+
let shouldCopy = this.props.shouldCopyOnOutsideDrop;
330+
if (typeof shouldCopy === 'function') {
331+
shouldCopy = shouldCopy({
331332
node,
332-
treeIndex: null,
333-
path: null,
334-
nextPath: null,
335-
nextTreeIndex: null,
336-
prevPath,
337-
prevTreeIndex,
333+
prevTreeIndex: treeIndex,
334+
prevPath: path,
335+
});
336+
}
337+
338+
let treeData = this.state.draggingTreeData || this.props.treeData;
339+
340+
// If copying is enabled, a drop outside leaves behind a copy in the
341+
// source tree
342+
if (shouldCopy) {
343+
treeData = changeNodeAtPath({
344+
treeData: this.props.treeData, // use treeData unaltered by the drag operation
345+
path,
346+
newNode: ({ node: copyNode }) => ({ ...copyNode }), // create a shallow copy of the node
347+
getNodeKey: this.props.getNodeKey,
338348
});
339349
}
350+
351+
this.props.onChange(treeData);
352+
353+
this.props.onMoveNode({
354+
treeData,
355+
node,
356+
treeIndex: null,
357+
path: null,
358+
nextPath: null,
359+
nextTreeIndex: null,
360+
prevPath: path,
361+
prevTreeIndex: treeIndex,
362+
});
340363
}
341364
}
342365

@@ -644,6 +667,13 @@ ReactSortableTree.propTypes = {
644667
// Determine whether a node can be dropped based on its path and parents'.
645668
canDrop: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
646669

670+
// When true, or a callback returning true, dropping nodes to react-dnd
671+
// drop targets outside of this tree will not remove them from this tree
672+
shouldCopyOnOutsideDrop: PropTypes.oneOfType([
673+
PropTypes.func,
674+
PropTypes.bool,
675+
]),
676+
647677
// Called after children nodes collapsed or expanded.
648678
onVisibilityToggle: PropTypes.func,
649679

@@ -671,6 +701,7 @@ ReactSortableTree.defaultProps = {
671701
searchFocusOffset: null,
672702
searchMethod: null,
673703
searchQuery: null,
704+
shouldCopyOnOutsideDrop: false,
674705
slideRegionSize: 100,
675706
style: {},
676707
};

0 commit comments

Comments
 (0)