From e29852860e69237fe90332959fa109b86304f2b5 Mon Sep 17 00:00:00 2001 From: Kyle Saxberg Date: Mon, 24 Sep 2018 18:42:57 -0700 Subject: [PATCH 1/5] create configurable canNodeHaveChildren function --- src/react-sortable-tree.js | 8 +++++ src/utils/dnd-manager.js | 8 ++++- stories/childless-nodes.js | 73 ++++++++++++++++++++++++++++++++++++++ stories/index.js | 6 ++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 stories/childless-nodes.js diff --git a/src/react-sortable-tree.js b/src/react-sortable-tree.js index 9d200429..dadd55b8 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) { 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/childless-nodes.js b/stories/childless-nodes.js new file mode 100644 index 00000000..d843cb6c --- /dev/null +++ b/stories/childless-nodes.js @@ -0,0 +1,73 @@ +import React, { Component } from 'react'; +import SortableTree, { changeNodeAtPath } 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: [{ + name: 'Managers', + expanded: true, + children: [{ + name: 'Rob', + children: [], + hasNoChildren: true + }, { + name: 'Joe', + children: [], + hasNoChildren: true + }] + }, { + name: 'Clerks', + expanded: true, + children: [{ + name: 'Bertha', + children: [], + hasNoChildren: true + }, { + name: 'Billy', + children: [], + hasNoChildren: true + }] + }], + }; + } + + render() { + const getNodeKey = ({ treeIndex }) => treeIndex; + return ( +
+
+ !node.hasNoChildren} + onChange={treeData => this.setState({ treeData })} + generateNodeProps={({ node, path }) => ({ + title: ( + { + const name = event.target.value; + + this.setState(state => ({ + treeData: changeNodeAtPath({ + treeData: state.treeData, + path, + getNodeKey, + newNode: { ...node, name }, + }), + })); + }} + /> + ), + })} + /> +
+
+ ); + } +} diff --git a/stories/index.js b/stories/index.js index 1c5f0a84..53110313 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'; @@ -86,3 +87,8 @@ storiesOf('Advanced', module) 'only-expand-searched-node.js' ) ); + +storiesOf('Custom', module) + .add('Some nodes should not preview drop as child', () => + wrapWithSource(, 'childless-nodes.js') + ) From 611c28a85e8003eab88a0c4b45a30b28543053f1 Mon Sep 17 00:00:00 2001 From: Kyle Saxberg Date: Mon, 24 Sep 2018 18:45:24 -0700 Subject: [PATCH 2/5] add to proptypes --- src/react-sortable-tree.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/react-sortable-tree.js b/src/react-sortable-tree.js index dadd55b8..6a6b10c9 100644 --- a/src/react-sortable-tree.js +++ b/src/react-sortable-tree.js @@ -867,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([ @@ -892,6 +895,7 @@ ReactSortableTree.propTypes = { ReactSortableTree.defaultProps = { canDrag: true, canDrop: null, + canNodeHaveChildren: () => true, className: '', dndType: null, generateNodeProps: null, From 8ed375a9fdf30e26449cee22af1c64f81969d3f3 Mon Sep 17 00:00:00 2001 From: Kyle Saxberg Date: Mon, 24 Sep 2018 19:00:08 -0700 Subject: [PATCH 3/5] simplify the documentation example --- stories/childless-nodes.js | 35 +++++++---------------------------- stories/index.js | 8 +++----- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/stories/childless-nodes.js b/stories/childless-nodes.js index d843cb6c..90c3f31c 100644 --- a/stories/childless-nodes.js +++ b/stories/childless-nodes.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import SortableTree, { changeNodeAtPath } from '../src'; +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'; @@ -9,26 +9,26 @@ export default class App extends Component { this.state = { treeData: [{ - name: 'Managers', + title: 'Managers', expanded: true, children: [{ - name: 'Rob', + title: 'Rob', children: [], hasNoChildren: true }, { - name: 'Joe', + title: 'Joe', children: [], hasNoChildren: true }] }, { - name: 'Clerks', + title: 'Clerks', expanded: true, children: [{ - name: 'Bertha', + title: 'Bertha', children: [], hasNoChildren: true }, { - name: 'Billy', + title: 'Billy', children: [], hasNoChildren: true }] @@ -37,7 +37,6 @@ export default class App extends Component { } render() { - const getNodeKey = ({ treeIndex }) => treeIndex; return (
@@ -45,26 +44,6 @@ export default class App extends Component { treeData={this.state.treeData} canNodeHaveChildren={node => !node.hasNoChildren} onChange={treeData => this.setState({ treeData })} - generateNodeProps={({ node, path }) => ({ - title: ( - { - const name = event.target.value; - - this.setState(state => ({ - treeData: changeNodeAtPath({ - treeData: state.treeData, - path, - getNodeKey, - newNode: { ...node, name }, - }), - })); - }} - /> - ), - })} />
diff --git a/stories/index.js b/stories/index.js index 53110313..04c171d7 100644 --- a/stories/index.js +++ b/stories/index.js @@ -86,9 +86,7 @@ storiesOf('Advanced', module) , 'only-expand-searched-node.js' ) - ); - -storiesOf('Custom', module) - .add('Some nodes should not preview drop as child', () => - wrapWithSource(, 'childless-nodes.js') ) + .add('Prevent some nodes from having children', () => + wrapWithSource(, 'childless-nodes.js') + ); From 64a2390569e3bf216f0111e17b5a0e98d962e181 Mon Sep 17 00:00:00 2001 From: Kyle Saxberg <15057490+ksaxberg@users.noreply.github.com> Date: Mon, 24 Sep 2018 19:48:24 -0700 Subject: [PATCH 4/5] add story, modify example slightly --- stories/__snapshots__/storyshots.test.js.snap | 584 ++++++++++++++++++ stories/childless-nodes.js | 10 +- 2 files changed, 589 insertions(+), 5 deletions(-) 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 index 90c3f31c..a9636ffb 100644 --- a/stories/childless-nodes.js +++ b/stories/childless-nodes.js @@ -14,11 +14,11 @@ export default class App extends Component { children: [{ title: 'Rob', children: [], - hasNoChildren: true + isPerson: true }, { title: 'Joe', children: [], - hasNoChildren: true + isPerson: true }] }, { title: 'Clerks', @@ -26,11 +26,11 @@ export default class App extends Component { children: [{ title: 'Bertha', children: [], - hasNoChildren: true + isPerson: true }, { title: 'Billy', children: [], - hasNoChildren: true + isPerson: true }] }], }; @@ -42,7 +42,7 @@ export default class App extends Component {
!node.hasNoChildren} + canNodeHaveChildren={node => !node.isPerson} onChange={treeData => this.setState({ treeData })} />
From 01147fb48e34066bee033ac49c93ba20c21c6f8c Mon Sep 17 00:00:00 2001 From: Kyle Saxberg Date: Thu, 4 Oct 2018 10:57:16 -0700 Subject: [PATCH 5/5] Add canNodeHaveChildren to props list in readme --- README.md | 1 + 1 file changed, 1 insertion(+) 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`. |