diff --git a/README.md b/README.md index e5d23a3d..2cbe9a0e 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ export default class Tree extends Component { | rowDirection | string | Adds row direction support if set to `'rtl'` Defaults to `'ltr'`. | | canDrag | func or bool | Return false from callback to prevent node from dragging, by hiding the drag handle. Set prop to `false` to disable dragging on all nodes. Defaults to `true`.
`({ node: object, path: number[] or string[], treeIndex: number, lowerSiblingCounts: number[], isSearchMatch: bool, isSearchFocus: bool }): bool`
| | canDrop | func | Return false to prevent node from dropping in the given location.
`({ node: object, prevPath: number[] or string[], prevParent: object, prevTreeIndex: number, nextPath: number[] or string[], nextParent: object, nextTreeIndex: number }): bool`
| +| canNodeHaveChildren | func | Function to determine whether a node can have children, useful for preventing hover preview when you have a `canDrop` condition. Default is set to a function that returns `true`. Functions should be of type `(node): bool`. | theme | object | Set an all-in-one packaged appearance for the tree. See the [Themes](#themes) section for more information. | | searchMethod | func | The method used to search nodes. Defaults to [`defaultSearchMethod`](https://github.com/frontend-collective/react-sortable-tree/blob/master/src/utils/default-handlers.js), which uses the `searchQuery` string to search for nodes with matching `title` or `subtitle` values. NOTE: Changing `searchMethod` will not update the search, but changing the `searchQuery` will.
`({ node: object, path: number[] or string[], treeIndex: number, searchQuery: any }): bool`
| | searchQuery | string or any | Used by the `searchMethod` to highlight and scroll to matched nodes. Should be a string for the default `searchMethod`, but can be anything when using a custom search. Defaults to `null`. | diff --git a/src/react-sortable-tree.js b/src/react-sortable-tree.js index 9d200429..6a6b10c9 100644 --- a/src/react-sortable-tree.js +++ b/src/react-sortable-tree.js @@ -479,6 +479,14 @@ class ReactSortableTree extends Component { this.moveNode(dropResult); } + canNodeHaveChildren(node) { + const { canNodeHaveChildren } = this.props; + if (canNodeHaveChildren) { + return canNodeHaveChildren(node); + } + return true; + } + // Load any children in the tree that are given by a function // calls the onChange callback on the new treeData static loadLazyChildren(props, state) { @@ -859,6 +867,9 @@ ReactSortableTree.propTypes = { // Determine whether a node can be dropped based on its path and parents'. canDrop: PropTypes.func, + // Determine whether a node can have children + canNodeHaveChildren: PropTypes.func, + // When true, or a callback returning true, dropping nodes to react-dnd // drop targets outside of this tree will not remove them from this tree shouldCopyOnOutsideDrop: PropTypes.oneOfType([ @@ -884,6 +895,7 @@ ReactSortableTree.propTypes = { ReactSortableTree.defaultProps = { canDrag: true, canDrop: null, + canNodeHaveChildren: () => true, className: '', dndType: null, generateNodeProps: null, diff --git a/src/utils/dnd-manager.js b/src/utils/dnd-manager.js index ea8f4562..469143fc 100644 --- a/src/utils/dnd-manager.js +++ b/src/utils/dnd-manager.js @@ -62,9 +62,15 @@ export default class DndManager { const rowAbove = dropTargetProps.getPrevRow(); if (rowAbove) { + let { path } = rowAbove; + const aboveNodeCannotHaveChildren = !this.treeRef.canNodeHaveChildren(rowAbove.node); + if (aboveNodeCannotHaveChildren) { + path = path.slice(0, path.length - 1); + } + // Limit the length of the path to the deepest possible dropTargetDepth = Math.min( - rowAbove.path.length, + path.length, dropTargetProps.path.length ); } diff --git a/stories/__snapshots__/storyshots.test.js.snap b/stories/__snapshots__/storyshots.test.js.snap index 62982cca..c92296c9 100644 --- a/stories/__snapshots__/storyshots.test.js.snap +++ b/stories/__snapshots__/storyshots.test.js.snap @@ -1044,6 +1044,590 @@ exports[`Storyshots Advanced Playing with generateNodeProps 1`] = ` `; +exports[`Storyshots Advanced Prevent some nodes from having children 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Rob + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Joe + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Bertha + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Billy + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + VIEW SOURCE → + +
+`; + exports[`Storyshots Advanced Touch support (Experimental) 1`] = `
diff --git a/stories/childless-nodes.js b/stories/childless-nodes.js new file mode 100644 index 00000000..a9636ffb --- /dev/null +++ b/stories/childless-nodes.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react'; +import SortableTree from '../src'; +// In your own app, you would need to use import styles once in the app +// import 'react-sortable-tree/styles.css'; + +export default class App extends Component { + constructor(props) { + super(props); + + this.state = { + treeData: [{ + title: 'Managers', + expanded: true, + children: [{ + title: 'Rob', + children: [], + isPerson: true + }, { + title: 'Joe', + children: [], + isPerson: true + }] + }, { + title: 'Clerks', + expanded: true, + children: [{ + title: 'Bertha', + children: [], + isPerson: true + }, { + title: 'Billy', + children: [], + isPerson: true + }] + }], + }; + } + + render() { + return ( +
+
+ !node.isPerson} + onChange={treeData => this.setState({ treeData })} + /> +
+
+ ); + } +} diff --git a/stories/index.js b/stories/index.js index 1c5f0a84..04c171d7 100644 --- a/stories/index.js +++ b/stories/index.js @@ -6,6 +6,7 @@ import AddRemoveExample from './add-remove'; import BarebonesExample from './barebones'; import CallbacksExample from './callbacks'; import CanDropExample from './can-drop'; +import ChildlessNodes from './childless-nodes'; import DragOutToRemoveExample from './drag-out-to-remove'; import ExternalNodeExample from './external-node'; import GenerateNodePropsExample from './generate-node-props'; @@ -85,4 +86,7 @@ storiesOf('Advanced', module) , 'only-expand-searched-node.js' ) + ) + .add('Prevent some nodes from having children', () => + wrapWithSource(, 'childless-nodes.js') );