Code Monkey home page Code Monkey logo

react-dnd-treeview's Introduction

React DnD TreeView

A draggable and droppable React treeview component.
You can use render props to create each node freely.

react-dnd-treeview

Demo and Examples

Some of the examples below use MUI(Material-UI) components, but TreeView does not depend on MUI, so you can use other libraries or your own custom components.

https://minop1205.github.io/react-dnd-treeview/

Breaking changes and migration

See Migration Guide for information on breaking changes and migrations between major versions.

Getting Started

Installation

$ npm i react-dnd @minoru/react-dnd-treeview

Usage

import { useState } from "react";
import {
  Tree,
  getBackendOptions,
  MultiBackend,
} from "@minoru/react-dnd-treeview";
import { DndProvider } from "react-dnd";
import initialData from "./sample-default.json";

function App() {
  const [treeData, setTreeData] = useState(initialData);
  const handleDrop = (newTreeData) => setTreeData(newTreeData);

  return (
    <DndProvider backend={MultiBackend} options={getBackendOptions()}>
      <Tree
        tree={treeData}
        rootId={0}
        onDrop={handleDrop}
        render={(node, { depth, isOpen, onToggle }) => (
          <div style={{ marginLeft: depth * 10 }}>
            {node.droppable && (
              <span onClick={onToggle}>{isOpen ? "[-]" : "[+]"}</span>
            )}
            {node.text}
          </div>
        )}
      />
    </DndProvider>
  );
}

Backends

MultiBackend is a backend to support both touch and pointer devices. If you only need support for one or the other, you can also use the backend provided by react-dnd-html5-backend or react-dnd-touch-backend.

import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

function App() {
  return (
    <DndProvider backend={HTML5Backend}>
      <Tree {...someProps}> />
    </DndProvider>
  );
}
import { DndProvider } from "react-dnd";
import { TouchBackend } from "react-dnd-touch-backend";

function App() {
  return (
    <DndProvider backend={TouchBackend}>
      <Tree {...someProps}> />
    </DndProvider>
  );
}

Backend options

HTML5Backend, TouchBackend and MultiBackend allow setting the BackendOptions defined in react-dnd.

For more information on TouchBackend, please see here. (For the HTML5Backend option, only the rootElement option is available.)

import { DndProvider } from "react-dnd";
import { HTML5Backend, HTML5BackendOptions } from "react-dnd-html5-backend";
import {TouchBackend, TouchBackendOptions} from "react-dnd-touch-backend"
import {Tree, MultiBackend, getBackendOptions} from "@minoru/react-dnd-treeview"

const touchOptions: Partial<TouchBackendOptions> = {
  // some options
};

const html5Options: Partial<HTML5BackendOptions> = {
  rootElement: document.body,
  // some options
};

const multiOptions = {
  touch: touchOptions,
  html5: html5Options,
}

function App() {
  return (
    <DndProvider
      backend={MultiBackend}
      options={getBackendOptions(multiOptions)}

      // or
      // backend={HTML5Backend}
      // options={html5Options}

      // or
      // backend={TouchBackend}
      // options={touchOptions}
    >
      <Tree {...someProps}> />
    </DndProvider>
  );
}

Data Structure

To display the treeview, pass data with the following structure to the tree property of the Tree component.

Basic example

The minimal data structure for representing the tree is shown in the following example

[
  {
    "id": 1,
    "parent": 0,
    "droppable": true,
    "text": "Folder 1"
  },
  {
    "id": 2,
    "parent": 1,
    "text": "File 1-1"
  },
  {
    "id": 3,
    "parent": 1,
    "text": "File 1-2"
  },
  {
    "id": 4,
    "parent": 0,
    "droppable": true,
    "text": "Folder 2"
  },
  {
    "id": 5,
    "parent": 4,
    "droppable": true,
    "text": "Folder 2-1"
  },
  {
    "id": 6,
    "parent": 5,
    "text": "File 2-1-1"
  }
]

Optional data

If you want to pass custom properties to each node's rendering,
you can use the data property.

[
  {
    "id": 1,
    "parent": 0,
    "droppable": true,
    "text": "Folder 1"
  },
  {
    "id": 2,
    "parent": 1,
    "text": "File 1-1",
    "data": {
      "fileType": "csv",
      "fileSize": "0.5MB"
    }
  },
  {
    "id": 3,
    "parent": 1,
    "text": "File 1-2",
    "data": {
      "fileType": "pdf",
      "fileSize": "4.8MB"
    }
  },
  {
    "id": 4,
    "parent": 0,
    "droppable": true,
    "text": "Folder 2"
  },
  {
    "id": 5,
    "parent": 4,
    "droppable": true,
    "text": "Folder 2-1"
  },
  {
    "id": 6,
    "parent": 5,
    "text": "File 2-1-1",
    "data": {
      "fileType": "image",
      "fileSize": "2.1MB"
    }
  }
]

Node properties

Key Type Required Default Description
id number | string - Identifier of each node
parent number | string - Parent id of each node
text string - Node label
droppable boolean false If true, child nodes will be accepted and it will be able to drop other node
data any undefined Additional data to be injected into each node.
These data are available in the render props.

Component API

Props Type Required Default Description
tree array The data representing the tree structure. An array of node data.
rootId number | string The id of the root node. It is the parent id of the shallowest node displayed in the tree view.
extraAcceptTypes array [] If allowing drop from outside the tree, the drag type of the drag source.
classes object undefined A set of CSS class names to be applied to a specific area in the tree view.
See the Component Styling section for more information.
listComponent string ul HTML tag for list.
listItemComponent string li HTML tag for list items.
render function The render function of each node.
Please refer to the Render prop section for more details about the render functions.
dragPreviewRender function undefined Render function for customizing the drag preview.
See the Dragging Preview section for more information on customizing the drag preview

NOTE:
The default preview is not displayed on touch devices. Therefore, if you want to support touch devices, please define a custom preview in dragPreviewRender.
onDrop function Callback function for when the state of the tree is changed.
The new data is passed as the argument.
See the onDrop callback section for more information.
onDragStart function undefined This event is fired when a node in the tree is started to be dragged. The event handler is passed the target node and a DragSourceMonitor object.
onDragEnd function undefined This event is fired when a node in the tree is finished being dragged. The event handler is passed the target node and a DragSourceMonitor object.
onChangeOpen function undefined Callback function to be called after the open/close state of a node is changed.
The function is passed an array of node IDs in the open state.
canDrop function undefined A callback function to determine if a given node can be dropped to another node.
If nothing is returned (or if undefined is returned), the default rules are followed.
If it returns true or false, the default rules will be overridden and the droppable properties of each node will not be referenced.
This callback takes the current tree and the same option object that is passed to the onDrop callback.
See the canDrop callback section for more information.
canDrag function undefined Callback function which should return true or false depending on if a give node should be draggable.
By default, all nodes are draggable.
sort function | boolean true This property controls the order of the child nodes.
By default (true), they are sorted by the text property of each node.
If false, sorting is disabled. In this case, the nodes will follow the order of the array passed to the tree property.
It is also possible to customize the sorting by passing a callback function.
insertDroppableFirst boolean true Specifies whether droppable nodes should be placed first in the list of child nodes.
enableAnimateExpand boolean false Specifies whether use animation when toggle expanding the node.
placeholderRender function undefined Render function for the drop destination placeholder. By default, placeholder is not displayed.
See the Manual sort with placeholder section for more information on using placeholder.
placeholderComponent string li HTML tag for placeholder.
dropTargetOffset number 0 Effective drop range of a droppable node. It is specified in pixels from the top or bottom of the node.
Used to insert a node anywhere using placeholders.

See the Manual sort with placeholder placeholder section for more information on using placeholder.
initialOpen boolean | array false If true, all parent nodes will be initialized to the open state.
If an array of node IDs is passed instead of the boolean value, only the specified node will be initialized in the open state. When this prop is updated the open state of the tree is also reset.
rootProps object undefined Properties to be passed to the root element (by default, ul tag), excluding the ref and role property.

Render prop

To render each tree node, please pass a render function to the render property.

<Tree
  {...props}
  render={(node, { depth, isOpen, draggable, onToggle }) => (
    <div style={{ marginLeft: depth * 10 }}>
      {node.droppable && (
        <span onClick={onToggle}>{isOpen ? "[-]" : "[+]"}</span>
      )}
      {node.text}
    </div>
  )}
/>

The arguments passed to the render function are as follows

Name Type Description
data object Node data. (an element in the tree data array)
options.depth number The depth of the node hierarchy.
options.isOpen boolean The open and closed state of the node.
If droppable is not true, isOpen is always false.
options.draggable boolean Indicates whether this node is draggable or not.
options.hasChild boolean Flag indicating whether or not the node has children. It is true if the node has children, false otherwise.
options.isDragging boolean Flag indicating whether this node is being dragged or not.
options.isDropTarget boolean Flag indicating whether or not this node is a drop target.
options.containerRef React.RefObject Reference to the HTML element (default: li tag) that wraps the custom node.
options.handleRef React.RefObject Reference to the HTML element you wish to set as a drag handle. It is used by assigning it to the ref attribute of the element you want to set as a handle.By default, no handle is set and the entire node is draggable.
See Drag handle for details.
options.onToggle function An event handler for the open/close button of a node.

Drag handle

By default, the entire node is draggable, but the handleRef render option allows the node to be dragged only by the specified handle, as in the following example.

<Tree
  {...props}
  render={(node, { handleRef }) => (
    <div>
      <span ref={handleRef}>[Drag me]</span>
      {node.text}
    </div>
  )}
/>

Dragging preview

By default, the drag preview is a screenshot of a DOM node.
The dragPreviewRender property allows you to display a custom React component instead of a screenshot.

NOTE:
The default preview is not displayed on touch devices.
Therefore, if you want to support touch devices, please define a custom preview in dragPreviewRender.

<Tree
  {...props}
  dragPreviewRender={(monitorProps) => {
    const item = monitorProps.item;

    return (
      <div>
        <p>{item.text}</p>
      </div>
    );
  }}
/>

The data passed to dragPreviewRender contains the following properties

Name Type Description
item object Node data. (an element in the tree data array)
It also includes the ref property, which is a reference to the HTML element to be dragged.
clientOffset object The client offset of the pointer during the dragging operation.
It is in the format of {x: number, y: number}.
If the item is not being dragged, it is set to null.

onDrop callback

If the tree is modified by drag-and-drop, the changes can be retrieved by the onDrop callback.

const [treeData, setTreeData] = useState(initialTreeData);
const handleDrop = (
  newTree,
  { dragSourceId, dropTargetId, dragSource, dropTarget }
) => {
  // Do something

  setTreeData(newTree);
};

return <Tree {...props} tree={treeData} onDrop={handleDrop} />;

The arguments passed to the onDrop callback function are as follows

Name Type Description
newTree array This data represents the updated TreeView.
To redraw the modified TreeView, you need to set this data to the tree property.
options.dragSourceId number | string | undefined node id of the dragging source.
If the drag source is an element external to DndProvider or a file or selected text, these will be undefined.
options.dropTargetId number | string node id of the drop destination.
If the drop destination is the root node, it will be the value of the rootId property.
options.dragSource object node item of the dragging source.
If the drag source is an element external to DndProvider or a file or selected text, these will be undefined.
options.dropTarget object | undefined node item of the drop destination.
If the drop destination is the root node, it will be undefined
options.destinationIndex number | undefined When the sort property is false, it indicates the index to which the drag source in the tree data array should be moved.
When the sort property is true, it will be undefined.
options.relativeIndex number | undefined When the sort property is false, it indicates the relative index of the drop destination with respect to the parent node.
When the sort property is true, it will be undefined.
options.monitor object Provides various methods for accessing react-dnd's internal state, e.g. for accessing drag sources from outside DndProvider.
See this definition for details.

canDrop callback

By default, it allows dropping to any droppable node (or root node) except its own descendants. This callback can override the default rules.

If it returns nothing or returns undefined, the default rules will be applied. If it returns a boolean value, it will override the default rules and the droppable property of each node will no longer be referenced.

If it returns false and the user drops the dragged node, no action will be taken and the onDrop callback will not be fired.

This callback takes the same parameters as the onDrop callback, but the first parameter specifies the current tree.

const canDrop = (
  currentTree,
  { dragSourceId, dropTargetId, dragSource, dropTarget }
) => {
  return true;

  // or

  return false;

  // or

  return;

  // or

  return undefined;
};

return <Tree {...props} tree={treeData} canDrop={canDrop} />;

NOTE:
When overriding the default rules by returning true or false, be careful of inconsistencies in the tree structure.
For example, if you allow dropping from a parent node to a child node as shown in the figure below, inconsistency will occur and the tree will collapse.

malformed tree

Manual sort with placeholder

By default, nodes are automatically sorted and cannot be sorted manually, but by combining some APIs, you can sort them manually and display placeholders as follows.

placeholder_sample

The following is an example (excerpt) of the implementation of manual sort of nodes and placeholder display.

import { CustomPlaceholder } from "./CustomPlaceholder";
import styles from "./App.module.css";

function App() {
  const [treeData, setTreeData] = useState(SampleData);
  const handleDrop = (newTree) => setTreeData(newTree);

  return (
    <Tree
      {...props}
      tree={treData}
      onDrop={handleDrop}
      classes={{
        placeholder: styles.placeholder,
      }}
      sort={false}
      insertDroppableFirst={false}
      canDrop={(tree, { dragSource, dropTargetId }) => {
        if (dragSource?.parent === dropTargetId) {
          return true;
        }
      }}
      dropTargetOffset={5}
      placeholderRender={(node, { depth }) => (
        <CustomPlaceholder node={node} depth={depth} />
      )}
    />;
  );
}

External drag source

To drop elements outside the tree into the tree, extraAcceptTypes must be set.

If the external drag source is under a DndProvider, set the type and item using useDrag in react-dnd for that element. add the external drag source type to extraAcceptTypes so it can be dropped in the tree.

If the external drag source is an element or file external to DndProvider, adding the type defined in react-dnd-html5-backend to extraAcceptTypes will allow dropping into the tree.

In this case, the onDrop callback will access the dropped element via options.monitor and add the new node to the data array of tree, as in the following example.

import { NativeTypes } from "react-dnd-html5-backend";

function App() {
  const [treeData, setTreeData] = useState(SampleData);
  const [lastId, setLastId] = useState(100);

  const handleDrop = (tree, options) => {
    const { dropTargetId, monitor } = options;
    const itemType = monitor.getItemType();

    if (itemType === NativeTypes.FILE) {
      const files = monitor.getItem().files;
      const nodes = files.map((file, index) => ({
        id: lastId + index,
        parent: dropTargetId,
        text: file.name,
      }));

      const mergedTree = [...tree, ...nodes];
      setTree(mergedTree);
      setLastId(lastId + files.length);
    } else {
      setTree(tree);
    }
  };

  return (
    <Tree
      {...someProps}
      tree={treeData}
      extraAcceptTypes={[NativeTypes.FILE]}

      {/*
        extraAcceptTypes={[NativeTypes.URL]}
        extraAcceptTypes={[NativeTypes.TEXT]}
        extraAcceptTypes={[NativeTypes.HTML]}
      */}

      onDrop={handleDrop}
    />;
  );
}

Component styling

You are free to define the styling of individual nodes in the tree in your Render props, but the rest of the tree can be styled by specifying the CSS class name for the classes property.

<Tree
  {...props}
  classes={{
    root: "my-root-classname",
    dragOver: "my-dragover-classname",
    // listItem: "my-listitem-classname",
    // You can use callback function for listItem class name.
    listItem: (node, options) => {
      return options.depth === 0
        ? "my-listitem-root-classname"
        : "my-listitem-classname";
    },
  }}
/>

You can use the following keys for the objects you pass to the classes property. Neither key is required.

Name type Description
root string CSS class name to give to the top-level container element (by default, ul tag) that wraps all nodes.
container string CSS class name to give to the element wrapping the list of nodes of the same hierarchy (by default, ul tag).
listItem string | function CSS class name to give to the element that wraps each node item (by default, li tag).
It is also possible to dynamically create class names using callback function.
dropTarget string CSS class name to give to the area that can be dropped during a node dragging operation.
draggingSource string CSS class name to give to the node during the dragging operation.
placeholder string CSS class name to give to the element wrapping the placeholder (by default, li tag).

Usage to open / close methods

The open/close status of a node is managed within the Tree component, but the methods for opening and closing nodes are public, so they can be controlled from outside the Tree component.

const ref = useRef(null);

const handleOpenAll = () => ref.current.openAll();
const handleCloseAll = () => ref.current.closeAll();

// open /close method can be passed a node ID or an array of node IDs
const handleOpen = (nodeId) => ref.current.open(nodeId);
const handleClose = (nodeId) => ref.current.close(nodeId);

<Tree
  ref={ref}
  {...props}
>

<button onClick={handleOpenAll}>Open All Folders</button>
<button onClick={handleCloseAll}>Close All Folders</button>
<button onClick={handleOpen}>Open specific folder(s)</button>
<button onClick={handleClose}>Close specific folder(s)</button>

License

MIT.

react-dnd-treeview's People

Contributors

abrman avatar bcheidemann avatar broncha avatar djpoida avatar kumar-parth avatar minop1205 avatar nuragic avatar percy507 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-dnd-treeview's Issues

Bug:canDropコールバックの dropTargetId, dropTarget パラメータが意図した値にならない

レポート

現状の動作

canDropコールバックをセットしても、node.droppable = false に設定されているノードでは監視対象にならず、その親ノードのID(またはオブジェクト)が返される。

期待する動作

ドロップターゲットのID/オブジェクトが返されること

参考情報

Node.tsx の以下のコードを

  if (item.droppable) {
    drop(drag(ref));
  } else {
    drag(ref);
  }

この様に修正する必要がある

  if (context.canDrop || item.droppable) {
    drop(drag(ref));
  } else {
    drag(ref);
  }

TODO

  • ドキュメント修正
  • droppableフラグの必須解除とデフォルト指定
  • Node.tsx の条件変更
  • Changelog修正
  • バージョンの修正

Adding Demo To Source Code

Would you be open to adding a working demo to the source code? I know demos are available on codepen, however, I wanted to fork the repo and play around with it on a localserver. Thanks!

機能要望:onDrop時にドロップ元とドロップ先を判別できるようにする

機能要望

概要

onDropコールバックの引数に src, dist を追加する。

目的・理由

現状だと onDrop コールバックには新しいツリーしか渡されないが、これだと「何が」「どこに」ドロップされたかの判別が難しい。実際の使用時にはそういった情報をサーバに送るケースが多いため。

実装案があれば

src, dist は node_id でも良いが、ツリーアイテムを渡した方が使い勝手が良いはず。

Feature:Add click event on the root UL element

Hey @minop1205 , just want to say that by far this is the best and most versatile tree component so far.
I would like to ask you if there's a way to add a click event on the root UL in order to unselect everything?
You can see that same functionality in VSCode by selecting a folder/file and then hitting the empty space below.

Error when using Tabs with different TreeViews

Hi @minop1205,

First of all, congratulations for the excellent package!

I'm trying to use Tabs (eg @mui/material/Tab) to navigate between different TreeViews. Let's say I have a set of tabs and each of them has a TreeView. When I open folders (or drag/drop files) from one tab, then go to another tab and go back to the previous tab, everything that was done in the first tab (open folders or drag/drop files) is undone.

I have created a codesandbox where this error can be verified: https://codesandbox.io/s/late-water-b7c7h

To reproduce the error, just follow these steps:

  1. In the TreeView of the first Tab, open directories and drag/drop files from one folder to another.
  2. Go to the second Tab, and then go back to the first Tab.
  3. Everything that was done in the first tab (opening folders and moving files) was undone.

Thanks.

Feature: Temporary disable dragging

Thanks for the great library!

We have editable nodes which transform to a TextField when clicking on an edit button. When selecting a text in the TextField via click + mouse movement, the node is dragged unintentionally.
image

To stop this behavior, we would like to disable dragging on all nodes while editing a node. We did not find any property to solve or workaround our problem.

A fix for our problem could be implemented by adding an canDrag property which can be false.

I look forward to realizing the request and can also provide support if needed.

Expected drag drop context error

Hello, a part of my codes leading error is showing below:

ReactDOM.render(


<Tree
tree={tree}
rootId={0}
render={(
node: NodeModel,
{ depth, isOpen, onToggle }
) => (

)}
sort={false}
dragPreviewRender={(
monitorProps: DragLayerMonitorProps
) => }
onDrop={handleDrop}
classes={{
root: styles.treeRoot,
draggingSource: styles.draggingSource,
dropTarget: styles.dropTarget
}}
initialOpen={true}
/>
,document.getElementById('projectPageContainer'));

and get this error:
Expected drag drop context
invariant
./src/index.ts:28
25 | );
26 | } else {
27 | let argIndex = 0;

28 | error = new Error(
| ^ 29 | format.replace(/%s/g, function() {
30 | return args[argIndex++];
31 | })
View compiled
▼ 21 stack frames were expanded.
useDragDropManager
./node_modules/@minoru/react-dnd-treeview/node_modules/react-dnd/dist/esm/hooks/useDragDropManager.js:12
useDragLayer
./node_modules/@minoru/react-dnd-treeview/node_modules/react-dnd/dist/esm/hooks/useDragLayer.js:22
useTreeDragLayer
http://localhost:8000/vendors~p__Project.js:7907:72
DragLayer
http://localhost:8000/vendors~p__Project.js:8146:22
renderWithHooks
./node_modules/react-dom/cjs/react-dom.development.js:14985
mountIndeterminateComponent
./node_modules/react-dom/cjs/react-dom.development.js:17811
beginWork
./node_modules/react-dom/cjs/react-dom.development.js:19049
HTMLUnknownElement.callCallback
./node_modules/react-dom/cjs/react-dom.development.js:3945
invokeGuardedCallbackDev
./node_modules/react-dom/cjs/react-dom.development.js:3994
invokeGuardedCallback
./node_modules/react-dom/cjs/react-dom.development.js:4056
beginWork$1
./node_modules/react-dom/cjs/react-dom.development.js:23964
performUnitOfWork
./node_modules/react-dom/cjs/react-dom.development.js:22776
workLoopSync
./node_modules/react-dom/cjs/react-dom.development.js:22707
renderRootSync
./node_modules/react-dom/cjs/react-dom.development.js:22670
performSyncWorkOnRoot
./node_modules/react-dom/cjs/react-dom.development.js:22293
scheduleUpdateOnFiber
./node_modules/react-dom/cjs/react-dom.development.js:21881
updateContainer
./node_modules/react-dom/cjs/react-dom.development.js:25482
(anonymous function)
./node_modules/react-dom/cjs/react-dom.development.js:26021
unbatchedUpdates
./node_modules/react-dom/cjs/react-dom.development.js:22431
legacyRenderSubtreeIntoContainer
./node_modules/react-dom/cjs/react-dom.development.js:26020

Prevent dragging nodes to the root

Hello, I have a tree with 1 root node, all other nodes come from 1 root node.

How can I make it so that any node below the 1st level cannot be dragged to the first level?

Support for mobile/touchevents

I came across this library yesterday when i was looking for a viable DnD tree solution. The features that this library provides handles all my usecases, except for the support for touchevents/mobile. I am wondering if you are considering supporting this feature sometime.

Personally, for my project, mobile/touch support is a critical requirement. So to test the viability, i went ahead and forked the library and tried some rudimentary fixes. react-dnd-touch-backend was required as a dependency. Please see my effort by installing:

"dependencies": {
    ....
    "@minoru/react-dnd-treeview": "https://github.com/arunmenon1975/react-dnd-treeview#touchsupport", 
    ....
    }

and adding the following 2 props - touchSupport and touchSupportOptions:

<Tree
  tree={treeData}
  touchSupport={true}
  touchSupportOptions={{
      "enableMouseEvents" : true
  }}
  rootId={0}
  onDrop={handleDrop}
  render={(node, {depth, isOpen, onToggle}) => (
    <div style={{marginLeft: depth * 10}}>
      {node.droppable && (
        <span onClick={onToggle}>
          {isOpen ? "[-]" : "[+]"}
        </span>
      )}
      {node.text.en}
    </div>
  )}
/>

It works on touch-enabled environments but there are issues, the biggest being that the Preview/Ghost component doesn't appear. I think integrating the recommendations at the react-dnd-touch-backend docs with this library can be slightly complex. Another option is to use react-dnd-preview from here which seems to automatically create the preview for the environment the user is in. But not sure if that would mean requiring a peer dependency or if it will need to be packaged along. This may also mean that dragPreviewRender may change or perhaps not be required.

Note: Adding react-dnd-touch-backend created a few typescript issues because of the version mismatches and thus i had to upgrade React/React-DOM/react-dnd-html5-backend/react-dnd to the latest versions. For some reason i also had to add @types/hoist-non-react-statics to the dev dependencies.

subscribe onDragStart & onDragEnd

Hi @minop1205,

Is there a way how i can subscribe on those events of ul/li?
I have my CustomNode.tsx, but if i add onDragStart and onDragEnd then it doesnt do anything.

Or may be can you pass in Node.tsx the ref?

    const params = {
        depth: props.depth,
        isOpen: open,
        hasChild,
        onToggle: handleToggle,
        ref: ref
    };

Then i can catch the ref in my CustomNode.tsx.

Feature Request: Initialize a subset of folders as open

Firstly, thanks for this awesome library! We're using it for a new feature at Components AI and it's working great. 🙏

I'm wondering if you'd be interested in being able to specify whether folders are open/closed as part of the tree data on load. Our use case is we want to store the state of the tree in the database so that when you reopen a document that state is preserved.

Hypothetically, it could be an isOpen boolean or something similar that tells the tree view to initialize the folder as open:

[
  {
    "id": 1,
    "parent": 0,
    "isOpen": true,
    "droppable": true,
    "text": "Folder 1"
  },
  {
    "id": 2,
    "parent": 1,
    "text": "File 1-1"
  },
  {
    "id": 3,
    "parent": 1,
    "text": "File 1-2"
  }
]

Anyway, thanks again for your hard work, and looking forward to your thoughts!

Release v1.1

  • update changelog
  • update csb samples
  • npm publish

`key` property in `Node` object

Hi! thanks for this great component!

I'm using a custom node and it's working great. Although, in web console it throws this warning.

image

It seems to be that Node doesn't have a unique key prop which is recommended to use in React apps.
It would be great to have this key and be able to pass it to custom nodes.

Great work!

Feature Request: Allow styling of the listItemComponent

Feature request

Summary

Allow styling of the listItemComponent when it is not a dragSource or dropTarget.

Purpose / Reason

We want to add a dashed 1px border to the listItemComponent when it is a dropTarget. To prevent the items from jumping around while dragging, we also need to add a transparent 1px border to all listItemComponents. Unfortunately, it is not possible to style the listItemComponent via the classes property.

If you have an implementation idea.

Allow setting default styles of the listItemComponent via the classes property.

Reference information

https://github.com/minop1205/react-dnd-treeview#component-styling

image

Adding elements to the tree

Hello, I tested the demo with adding elements, everything works great.

But this addition works on the button. The task is this:

  1. There is a simple html tree without dnd
  2. There is your tree.
  3. I need to implement adding elements from the first tree to yours using dnd

The point of the idea is that there is no need to remove an element from the first tree. The first tree is like a set of features. When dragging a feature onto your tree, I need to figure out what level I'm at and add a new element based on the dragged feature.

How can this be implemented?

Is it possible to use the react-dnd library for the first tree, and when dragging onto your tree, I could understand to which level I dragged an element from the first list?

Or do I need to create a custom node and wrap it additionally in react-dnd?

Feature Request: add isDragging and isDropTarget node properties

Add props isDragging and isDropTarget - like 'dropable' prop, which show the current state of a node if it is a drop target or on dragging.
This will add the ability to style a specific node, not a whole sub-tree, like with current classes draggingSource and dropTarget.

Asking about props because I`m using styled-components in a project, but classes will help as well :)

Node know whether it has child or not

Hi, Thank you for your great headless component

I was just wondering if you don't mind to return hasChild also in the render params :)
So every node knew whether it has child or not :)

Need it to hide or show chevron :)

Thanks,

Hari

Open Folder Option

Hi,
I was playing with this component and my remark is it would be great if add open option in tree data in order to open a folder on initial load

ERROR: Expected drag and drop context

I also experience this issue (as some others seem to do). Tried to install [email protected] still does not work. Also, going back to dnd 13 is a problem as per renzhiVivio comment.

Any chance of a new release addressing this issue? Thanks

Bug report: Dragging not possible after editing a node without hovering in Firefox

Report

Current behavior

unlock after editing only works when the input gets unhovered or unfocused while saving (in Firefox).

The useDragControl controls a lock which should be relased after editing. The unlock is trigged by handleMouseOut and handleFocusOut.
When working only with the keyboard as in the sandbox, mouseout does not work. focusout seems to be trigged in Chrome when the input gets unmounted from dom, but not in Firefox. Therefore we cannot unlock the tree in Firefox without using the mouse or simulating an onblur event before removing the input.

Expected behavior

unlock should also work when the input element is unmounted, independent from mouse position or focus state.

How to reproduce

https://codesandbox.io/s/editable-ts-forked-zg5md?file=/src/App.tsx

  • Open Firefox
  • Click the edit button
  • Do NOT hover the input
  • Press enter to save the node
  • Dragging is not possible

リファクタリング:ジェネリクスの型をanyにすべきか

リファクタリング

概要

現状、ジェネリクスの引数がunknown型になっているが、これだと利用する際に型を用意しないとエラーになる。
使い勝手を考えると unknown ではなく any にした方が良いのかもしれない。

目的・理由

ライブラリの使い勝手向上のため

手順や参考情報があれば

anyの場合、ジェネリクスに型を渡せばサジェストされるし、型を渡さなくてもエラーにはならない。
ただし、ライブラリ側でanyを許可することになってしまう。

Should be dropped between 2 nodes

I mean, we have a tree likes that:
--node 1
--node 2
--node 3

I drag and drop the node 3 between node 1 and node 2, it becomes:
--node 1
--node 3
--node 2

Thanks

Disable Sorting & Drag/Drop Between Nodes

Hi,

I was just wondering if there was a way to disable the automatic sorting of the nodes or to provide a n order in which I want them to be sorted e.g. an index property on the node would be great. I didn't see anything in the docs so unless I'm missing anything I guess this is a feature request! =P

Additionally, it would be great if you supported dropping between nodes - this would be really useful in combination with the above feature request - especially for my use case!

Thanks,

Ben

Add tree items on the fly and the main item (folder) is open

Hi again @minop1205,

Im trying to add folder and 2 files on existing tree and i want that folder will be opened after i add it.
Is there a way how i can do that?

i tried this below but this dont work.

{
{id: 3, parent:1, text: 'Folder', droppable: true, isOpen: true},
{id: 4, parent:3, text: 'File 2', droppable: false},
{id: 5, parent:3, text: 'File 2', droppable: false},
}

Feature: drag from and drop to callback/handle

Hi there,

It would be great to get a specific onDropItem handle on each node to get the from and to.

Example

{
  from: {
    id: 2,
    parent: 1,
    droppable: false,
    text: 'File 1-1',
  },
  to: {
    id: 4,
    parent: 0,
    droppable: true,
    text: 'Folder 2',
  }
}

Is this possible?

Delete item

Hi, is there a way to delete an item? If i put tree into state and pass the state to Tree afterwards remove an item the render isnt triggered. So the object is gone from the state but nothing is changed in DOM.
Is there a way how can i fix it?

Drop events do not register when sourceNode.parent === targetId

if (sourceNode === undefined || sourceNode.parent === targetId) {

Hi,

I understand why this has been implemented in this way, however, it would be great if there were a way to disable this behaviour. I can't share the true reason why I'm flagging up this behaviour as it's todo with a trade secret of my company. However, this could be useful to people for a variety of reasons e.g. If you'd like to display an warning/error (or play a sound) to the user when they try to carry out this action.

Current Behaviour
Dropping a child node onto it's parent does not result in the onDrop callback being called.

Desired Behaviour
Dropping a child node onto it's parent results in the onDrop callback being called.

Many thanks,

Ben

Sortable view

Hi,
Thanks for the amazing library. Just a question. Imagine that I have a simple two-level structure (folders + files in it). How can I use read-dnd-treeview to sort the folders or the files within them?

Eg:

| Folder 1
    | File 1
    | File 2
    | File 3
| Folder 2
    | File 4
    | File 5

I want the interface to not allow dragging Folder 1 within Folder 2 or files in Folder 1 within Folder 2, but I do want to be able to drag File 1, File 2 and File 3 within Folder 1 in order to rearrange them or to drag Folder 2 above Folder 1 in order to have something like this:

| Folder 2
    | File 4
    | File 5
| Folder 1
    | File 2
    | File 1
    | File 3

Is this currently possible? How? Thanks

Error during installation, React 17

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Other... Please describe:

Current behavior

When installed via 'npm install @minoru/react-dnd-treeview' got errors:

npm WARN ERESOLVE overriding peer dependency npm WARN Found: [email protected] npm WARN node_modules/react npm WARN peer react@"*" from @testing-library/[email protected] npm WARN node_modules/@testing-library/react npm WARN @testing-library/react@"^11.2.7" from the root project npm WARN 6 more (react-dom, react-scripts, recoil, the root project, ...) npm WARN npm WARN Could not resolve dependency: npm WARN peer react@"^16.13" from [email protected] npm WARN node_modules/react-dnd-multi-backend npm WARN react-dnd-multi-backend@"^6.0.2" from @minoru/[email protected] npm WARN node_modules/@minoru/react-dnd-treeview npm WARN ERESOLVE overriding peer dependency npm WARN Found: [email protected] npm WARN node_modules/react-dnd-html5-backend npm WARN react-dnd-html5-backend@"^14.0.1" from @minoru/[email protected] npm WARN node_modules/@minoru/react-dnd-treeview npm WARN @minoru/react-dnd-treeview@"*" from the root project npm WARN npm WARN Could not resolve dependency: npm WARN peer react-dnd-html5-backend@"^11.1.3" from [email protected] npm WARN node_modules/react-dnd-multi-backend npm WARN react-dnd-multi-backend@"^6.0.2" from @minoru/[email protected] npm WARN node_modules/@minoru/react-dnd-treeview npm WARN ERESOLVE overriding peer dependency npm WARN Found: [email protected] npm WARN node_modules/react-dnd-touch-backend npm WARN react-dnd-touch-backend@"^14.1.0" from @minoru/[email protected] npm WARN node_modules/@minoru/react-dnd-treeview npm WARN @minoru/react-dnd-treeview@"*" from the root project npm WARN npm WARN Could not resolve dependency: npm WARN peer react-dnd-touch-backend@"^11.1.3" from [email protected] npm WARN node_modules/react-dnd-multi-backend npm WARN react-dnd-multi-backend@"^6.0.2" from @minoru/[email protected] npm WARN node_modules/@minoru/react-dnd-treeview npm WARN ERESOLVE overriding peer dependency npm WARN Found: [email protected] npm WARN node_modules/react-dom npm WARN peer react-dom@"*" from @testing-library/[email protected] npm WARN node_modules/@testing-library/react npm WARN @testing-library/react@"^11.2.7" from the root project npm WARN 2 more (the root project, @minoru/react-dnd-treeview) npm WARN npm WARN Could not resolve dependency: npm WARN peer react-dom@"^16.13" from [email protected] npm WARN node_modules/react-dnd-multi-backend npm WARN react-dnd-multi-backend@"^6.0.2" from @minoru/[email protected] npm WARN node_modules/@minoru/react-dnd-treeview npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! Found: [email protected] npm ERR! node_modules/react npm ERR! peer react@"*" from @testing-library/[email protected] npm ERR! node_modules/@testing-library/react npm ERR! @testing-library/react@"^11.2.7" from the root project npm ERR! peer react@"17.0.2" from [email protected] npm ERR! node_modules/react-dom npm ERR! peer react-dom@"*" from @testing-library/[email protected] npm ERR! node_modules/@testing-library/react npm ERR! @testing-library/react@"^11.2.7" from the root project npm ERR! react-dom@"^17.0.2" from the root project npm ERR! 1 more (@minoru/react-dnd-treeview) npm ERR! 5 more (react-scripts, recoil, the root project, ...) npm ERR! npm ERR! Could not resolve dependency: npm ERR! peer react@"^16.13.1" from [email protected] npm ERR! node_modules/react-dnd-multi-backend/node_modules/react-dnd-preview npm ERR! react-dnd-preview@"^6.0.2" from [email protected] npm ERR! node_modules/react-dnd-multi-backend npm ERR! react-dnd-multi-backend@"^6.0.2" from @minoru/[email protected] npm ERR! node_modules/@minoru/react-dnd-treeview npm ERR! @minoru/react-dnd-treeview@"*" from the root project npm ERR! npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force, or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Full report:
eresolve-report.txt

Environment


Libs:
- react version: 17.0.2


For Tooling issues:
- Node version: 16.5.0
- NPM version: 7.20.2
- Platform:  Windows

Feature: add callback prop when changing open item IDs

Thank you for this fantastic library! 🙌

One thing that I think it would be great to add is a function prop that is called whenever an item is toggled between being open and being closed, something like:

<Tree
  {...props}
  onChangeOpen={(newOpenIds: TreeModel["id"][]) => {
    // save this to the user's browser, so it can be restored next time
  }}
/>

This would be useful for serialising the current open IDs state to LocalStorage, so that when the user refreshes the page, it can be passed to initialOpen, and the collapsed state of the tree is preserved between sessions!

What do you think? 🙂

Drop is canceled even though placeholder is visible

From PR below
#99 (comment)

When Tree component is used to manual sort items, the user might drag items outside of the droppable area -- especially when trying to move it to the top or the bottom of the list. If we restrict the drop to only the tree area, we might have some weird behaviors, like rendering the placeholder somewhere, but not accepting a drop when the user releases
the mouse button for example.

The default is still only allowing drop inside the tree area, but by setting cancelOnDropOutside as false we allow drops outside of the tree whenever there's a placeholder being rendered.

This is an example of the original issue:

demo.mov

Feature request: drag items by handle

Feature requests

Overview

An option to add a handle to each row to start dragging. Instead of start dragging by clicking anywhere in the row add a new element and only start dragging by clicking that handle.

Purpose / Reason

In mobile devices sometimes scrolling over a tree (with the finger) is interpreted as a drag-and-drop action and the tree is messed.

Any implementation ideas?

It would be nice if in the render function the handle could be defined.

Reference information

I have read the project is using react-dnd for drag and drop functionality. Here is an example of react-dnd
https://codesandbox.io/s/github/react-dnd/react-dnd/tree/gh-pages/examples_hooks_js/05-customize/handles-and-previews (it would be the example with the green box)
Another project that supports this is: dnd-kit
https://5fc05e08a4a65d0021ae0bf2-bphcuqqqih.chromatic.com/?path=/docs/examples-tree-sortable--all-features
In this example the handle can is more evident.

Bug:rootIdを0以外の値にするとルートコンテナへのドロップが出来ない

レポート

現状の動作

rootIdを0以外の値にするとルートコンテナへのドロップが出来ない

期待する動作

0以外の値(例えば文字列など)を設定してもドロップ可能になること

参考情報

Container.tsx の54行目の判定が正しくない。

TODO

  • TreeContext / TreeProps の型定義リファクタリング
  • Container.tsx Line: 54 の判定条件修正
  • 文字列rootIdのテスト追加
  • Changelog修正

Question - Reorder items in same level

Thank you for great library. could i know that is there way to reorder items in same level ?

as a example , if i drag and drop a item to same level ,currently it is dropped as subitem of target.
is it possible to drop item without creating sub item

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.