minop1205 / react-dnd-treeview

A draggable / droppable React-based treeview component. You can use render props to create each node freely.
MIT License
515 stars 69 forks source link

Restrict to the same hierarchy level while drag and drop (just sort the order) #225

Closed GVSSri closed 3 months ago

GVSSri commented 4 months ago

Is any other way to restrict moving the child to another parent. Want to maintain the hierarchy level within we can perform drag and drop

Please help on this

jooyeal commented 4 months ago

I think you can use the canDrop prop. If you want to prevent moving the child node to another parent, just return true only if the parent's ID is the same.

canDrop={(tree, { dragSource, dropTargetId }) => {
    if (dragSource?.parent === dropTargetId) {
        return true;
    }
}}
GVSSri commented 4 months ago

I tried but its not working as expected. My req is to maintain the hierarchy level during drag and drop, meaning a node can only be moved within its current level (siblings), Any help much appreciated

jooyeal commented 4 months ago

@GVSSri

Okay, if you just want to move nodes within their current level, I think you have to create a custom handleDrop callback function. You can sort nodes within that function.

this is a sample code

sample data

const initialData: NodeModel[] = [
  {
    id: 1,
    parent: 0,
    droppable: true,
    text: "Folder 1",
  },
  {
    id: 2,
    parent: 1,
    droppable: true,
    text: "File 1-1",
  },
  {
    id: 3,
    parent: 1,
    droppable: true,
    text: "File 1-2",
  },
  {
    id: 4,
    parent: 1,
    droppable: true,
    text: "File 1-3",
  },
  {
    id: 5,
    parent: 1,
    droppable: true,
    text: "File 1-4",
  },
  {
    id: 6,
    parent: 1,
    droppable: true,
    text: "File 1-5",
  },
  {
    id: 7,
    parent: 1,
    droppable: true,
    text: "File 1-6",
  },
  {
    id: 8,
    parent: 1,
    droppable: true,
    text: "File 1-7",
  },
  { id: 9, parent: 0, droppable: true, text: "Folder 2" },
];

sort function and custom handleDrop function

function moveElement<T>(array: T[], fromIndex: number, toIndex: number): T[] {
  const element = array.splice(fromIndex, 1)[0];
  array.splice(toIndex, 0, element);
  return array;
}

const handleDrop = (_: NodeModel[],  { dragSource, dropTarget }: DropOptions) => {
    const dragSourceIndex = treeData.findIndex((item) => item.id === dragSource?.id);
    const dropTargetIndex = treeData.findIndex((item) => item.id === dropTarget?.id);
    setTreeData((prev) => moveElement(prev, dragSourceIndex, dropTargetIndex));
};

usage as component props

<DndProvider backend={MultiBackend} options={getBackendOptions()}>
      <Tree
        tree={treeData}
        rootId={0}
        onDrop={handleDrop}
        sort={false}
        canDrop={(_, { dragSource, dropTarget }) => {
          if (dragSource?.id === dropTarget?.id) {
            return false;
          }

          if (dragSource?.parent === dropTarget?.parent) {
            return true;
          }

          return false;
        }}
        render={(node, { depth, isOpen, onToggle }) => (
          <div key={node.id} style={{ marginLeft: depth * 10 }}>
            {node.droppable && (
              <span onClick={onToggle}>{isOpen ? "[-]" : "[+]"}</span>
            )}
            {node.text}
          </div>
        )}
      />
</DndProvider>

I hope it was helpful to you. Thank you

minop1205 commented 3 months ago

@GVSSri To allow only sorting of nodes in the same hierarchy, the following API must be specified.

          <Tree
            tree={treeData}
            rootId={0}
            render={(node, { depth, isOpen, onToggle }) => (
              <CustomNode
                node={node}
                depth={depth}
                isOpen={isOpen}
                onToggle={onToggle}
              />
            )}
            dragPreviewRender={(monitorProps) => (
              <CustomDragPreview monitorProps={monitorProps} />
            )}
            onDrop={handleDrop}
            classes={{
              root: styles.treeRoot,
              draggingSource: styles.draggingSource,
              placeholder: styles.placeholderContainer,
            }}
            sort={false}
            insertDroppableFirst={false}
            canDrop={(tree, { dragSource, dropTargetId, dropTarget }) => {
              return dragSource?.parent === dropTargetId;
            }}
            dropTargetOffset={10}
            placeholderRender={(node, { depth }) => (
              <Placeholder node={node} depth={depth} />
            )}
            initialOpen={true}
          />

I think this Codesandbox will be helpful. https://codesandbox.io/p/sandbox/issue225-pd3536?file=%2Fsrc%2FApp.tsx%3A31%2C26

if you can't solve your problem yet, please reopen this issue.

thank you.