Skip to content

Commit 84a0565

Browse files
committed
docs: dnd overview
1 parent 86208f2 commit 84a0565

File tree

4 files changed

+167
-27
lines changed

4 files changed

+167
-27
lines changed

packages/docs/docs/contributing/1-tests.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ This is automatically done before each test for the initially rendered tree.
6666
You can see and experiment with the tree variant that is used during tests with this storybook
6767
instance:
6868

69-
https://headless-tree.lukasbach.com/storybook/react/?path=/story/react-misc-sync-tree-used-in-unit-tests--unit-test-async
69+
- [Async Tree](https://headless-tree.lukasbach.com/storybook/react/?path=/story/react-misc-async-tree-used-in-unit-tests--unit-test-async)
70+
- [Sync Tree](https://headless-tree.lukasbach.com/storybook/react/?path=/story/react-misc-sync-tree-used-in-unit-tests--unit-test-sync)
7071

7172
# Debugging tests
7273

Lines changed: 153 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,202 @@
11
---
22
slug: "/dnd/overview"
33
title: "Overview"
4+
hide_title: true
45
category: draganddrop
56
---
67

78
import { DemoBox } from "../../src/components/demo/demo-box";
9+
import {FeaturePageHeader} from "../../src/components/docs-page/feature-page-header";
10+
11+
<FeaturePageHeader
12+
title="Drag and Drop"
13+
subtitle="Drag-and-drop Capabilities for tree items and external drag objects"
14+
feature="drag-and-drop"
15+
/>
816

917
# Overview
1018

19+
<DemoBox tags={["feature/dnd"]} />
20+
21+
The Drag-And-Drop Feature provides drag-and-drop capabilities. It allows to drag a single tree item (or many of the
22+
[selection feature](/features/selection) is included in the tree config) and drop it somewhere else in the tree.
23+
The feature also allows you to create interactions between the tree and external drag objects, allowing you to drag
24+
tree items out of the tree, or foreign data objects from outside the tree inside. As extension of that, this can
25+
also be used to implement drag behavior between several trees.
26+
27+
This page explains the general idea of how Dnd works in Headless Tree, and how to get started with dragging items
28+
within a tree. The guides following after will go into more depth about
29+
30+
- TODO
31+
32+
## Configuring Drag and Drop
33+
34+
The gist of configuring Drag and Drop in Headless Tree, is to
35+
36+
- add the `dragAndDropFeature` to the tree config,
37+
- define the indent of your tree items in the config variable `indent`, and
38+
- handle the `onDrop` event in the tree config.
39+
40+
Note that the `indent` property is not required in the core tree configuration, since you can just use a custom
41+
indentation when rendering your items. For Drag and Drop it is required however to determine the drop position
42+
when the user is trying to reparent an item. (TODO link)
43+
44+
<!-- TODO render types as interfaces in typedoc, then fix links here -->
45+
The Dnd Feature also exposes a [Dnd State](/api/core#DndState) that you can [handle yourself if you want to](/guides/state).
46+
1147
```jsx typescript
1248
import {
1349
syncDataLoaderFeature,
14-
createOnDropHandler,
1550
dragAndDropFeature,
16-
hotkeysCoreFeature,
1751
selectionFeature,
1852
} from "@headless-tree/core";
1953

2054
const tree = useTree<Item>({
2155
indent: 20,
2256
canReorder: true,
23-
onDrop: createOnDropHandler((item, newChildren) => {
24-
// set newChildren to item
25-
}),
57+
onDrop: (items, target) => {
58+
// handle drop
59+
},
2660
features: [
2761
syncDataLoaderFeature,
2862
selectionFeature,
29-
hotkeysCoreFeature,
3063
dragAndDropFeature,
3164
],
3265
});
3366
```
3467

68+
## The Drop Event
69+
70+
When the user drops tree items onto a target within the tree, the [`onDrop`](/api/core/interface/dragAndDropFeatureConfig#onDrop) event is called
71+
with the list of items that were dragged, and a [drop target](/api/core#DropTarget). This drop target is an object that contains
72+
either
73+
74+
- the `item` that is dropped to, if the user dragged on the title of an item with the intent to make it a child of that item
75+
76+
or, if the user dragged between items while the `canReorder` config option is set to true:
77+
78+
- the `item` that should become the new parent
79+
- the `childIndex`, describing the index within the `item` where the user dropped
80+
- the `insertionIndex`, describing the index within the `item` where the items should be placed (see details below)
81+
- `dragLineIndex` and `dragLineLevel` describing the position of the drag line that is rendered while dragging
82+
83+
:::info Difference between `childIndex` and `insertionIndex`
84+
85+
`childIndex` describes the visual location inside the item that is being dropped into, where the user is dragging
86+
the items to. `insertionIndex` on the other hand also respects the special case that the user might drag items within
87+
the same folder from further up in a folder, to further down in the same folder.
88+
89+
If items are dragged from a different location into a new folder, `childIndex` and `insertionIndex` will be the same.
90+
However, if the two top-most items in a folder are dragged two items down, `childIndex` will be 4, but `insertionIndex`
91+
will be 2, since removing those two items will shift the items up by two.
92+
93+
:::
94+
95+
## Rendering a Tree with Drag and Drop
96+
97+
The only difference between rendering a normal HT Tree, and rendering one with Drag and Drop capabilities, is that you
98+
need to render a drag line that shows the user where the items will be dropped. You just need to render an additional
99+
div with the style `tree.getDragLineStyle()`, and style it however you want. The computed stylings will automatically
100+
add `display: none` if the user is not currently dragging items, and will compute relative positioning based on the
101+
item rendered with `tree.getContainerProps()`.
102+
103+
Below are examples of how to render a tree with Drag and Drop capabilities, as well as the exemplary CSS stylings
104+
used in the demo.
105+
35106
```jsx
36107
<div {...tree.getContainerProps()} className="tree">
37108
{tree.getItems().map((item) => (
38-
<button
39-
{...item.getProps()}
40-
key={item.getId()}
41-
style={{ paddingLeft: `${item.getItemMeta().level * 20}px` }}
42-
>
43-
<div
44-
className={`folder ${item.isFolder() && "folder"}`}
45-
>
46-
{item.getItemName()}
47-
</div>
48-
</button>
109+
<MyTreeItem key={item.id} item={item} />
49110
))}
50111
<div style={tree.getDragLineStyle()} className="dragline" />
51112
</div>
52113
```
53114
54-
<DemoBox tags={["feature/dnd"]} stories={["react-drag-and-drop-basic--basic"]} />
115+
```css
116+
.dragline {
117+
position: absolute;
118+
height: 2px;
119+
width: unset;
120+
margin-top: -1px;
121+
background-color: #00aff4;
122+
}
55123
56-
## Updated caches in async data loaders
124+
/* Render the circle at the left of the dragline */
125+
.dragline::before {
126+
content: "";
127+
position: absolute;
128+
left: 0;
129+
top: -3px;
130+
height: 4px;
131+
width: 4px;
132+
background: #fff;
133+
border: 2px solid #00aff4;
134+
border-radius: 99px;
135+
}
136+
```
137+
138+
139+
## Mutating your data after the Drop
140+
141+
When the drop event is registered with `onDrop`, you will likely want to mutate your data source to reflect
142+
the new order of items after the drag. Headless Tree provides three utility methods that make this very easy:
143+
144+
- [`removeItemsFromParents(movedItems, onChangeChildren)`](/api/core/function/removeItemsFromParents): Calls the provided
145+
`onChangeChildren` handler to remove the items defined in the first argument from their parents.
146+
- [`insertItemsAtTarget(itemIds, target, onChangeChildren)`](/api/core/function/insertItemsAtTarget): Calls the provided
147+
`onChangeChildren` handler to insert the items defined in the first argument at the target defined in the second argument.
148+
- [`createOnDropHandler(onChangeChildren)`](/api/core/function/createOnDropHandler): Combines the two methods above to
149+
create a drop handler that removes the items from their parents and inserts them at the target. This can directly be passed to the `onDrop` config option.
150+
151+
`itemIds` and `movedItems` are arrays of item instances, and thus the same type as what the `onDrop` config option provides.
152+
Similarly, `target` is the same type as the drop target that is provided in the `onDrop` event.
153+
154+
In most situations, it is sufficient to call `createOnDropHandler` with a function that mutates your data source, and
155+
use that to handle drop events of items within a single tree. The other methods are useful for interactions between
156+
several trees (imagine items being removed from one tree and then inserted in another), handling cases where foreign
157+
data is dragged into a tree to create a new tree item or tree items being dragged out of the tree to external drop targets,
158+
or handling other more complicated use cases.
159+
<!-- TODO add links to other guides for those cases -->
57160

58161
```jsx typescript
162+
import { createOnDropHandler } from "@headless-tree/core";
163+
59164
const tree = useTree<Item>({
60165
indent: 20,
61166
canReorder: true,
62-
onDrop: createOnDropHandler((item, newChildren) => {}),
167+
onDrop: createOnDropHandler((item, newChildren) => {
168+
myData[item.getId()].children = newChildren;
169+
}),
63170
features: [
64-
asyncDataLoaderFeature,
171+
syncDataLoaderFeature,
65172
selectionFeature,
66-
hotkeysCoreFeature,
67173
dragAndDropFeature,
68174
],
69175
});
70176
```
71177

72-
## Reading DragAndDrop state
178+
## Updated caches in async data loaders
179+
180+
:::info
181+
182+
You can ignore this when using `removeItemsFromParents`, `removeItemsFromParents` or `createOnDropHandler` to handle
183+
the drop event, as those handlers do this for you.
184+
185+
:::
186+
187+
When you handle the drop event yourself while using the async data loader, note that the async data loader caches
188+
tree data and does not automatically update the cache when you mutate the data source.
189+
190+
When you update the childrens assigned to an item as response to a drop event, you can update the cache with
191+
[`item.updateCachedChildrenIds(children: string[])`](/api/core/interface/AsyncDataLoaderFeatureItemInstance#updateCachedChildrenIds):
192+
193+
```typescript
194+
// Update your data source
195+
myData[parent.getId()].children = newChildren;
196+
197+
// Update cache of async data loader
198+
parent.updateCachedChildrenIds(newChildren);
199+
200+
// Trigger the recomputation of the internal tree structure
201+
tree.rebuildTree();
202+
```

packages/docs/docs/features/5-dnd.mdx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ import {FeaturePageHeader} from "../../src/components/docs-page/feature-page-hea
1313
subtitle="Drag-and-drop Capabilities for tree items and external drag objects"
1414
feature="drag-and-drop"
1515
/>
16+
1617
<DemoBox tags={["feature/dnd"]} />
1718

18-
TODO
19+
The Drag-And-Drop Feature provides drag-and-drop capabilities. It allows to drag a single tree item (or many of the
20+
[selection feature](/features/selection) is included in the tree config) and drop it somewhere else in the tree.
21+
The feature also allows you to create interactions between the tree and external drag objects, allowing you to drag
22+
tree items out of the tree, or foreign data objects from outside the tree inside. As extension of that, this can
23+
also be used to implement drag behavior between several trees.
24+
25+
Since this feature composes a large part of the functionality of Headless Tree, it is documented in its own section
26+
"Drag and Drop" on the left, get started with the [Drag and Drop Overview Page](/dnd/overview).

packages/docs/docs/features/99-main.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import { FeaturePageHeader } from "../../src/components/docs-page/feature-page-h
1515
isCore={true}
1616
/>
1717

18-
1918
<DemoBox tags={["feature/main"]} />
2019

21-
TODO
20+
The main feature provides the core functionality of Headless Tree, enabling most functions that are
21+
required by other features to work. Similar to the [Tree Feature](/features/tree), the main feature
22+
is included automatically and does not need to be actively imported.

0 commit comments

Comments
 (0)