react-arborist 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -84
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/module.js +7 -4
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +16 -13
- package/package.json +4 -3
- package/src/components/tree.tsx +21 -6
- package/src/tree-api.ts +2 -2
- package/src/types.ts +17 -13
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<h1>React Arborist</h1>
|
|
4
|
+
|
|
5
|
+
A full-featured tree component for React.
|
|
6
|
+
|
|
7
|
+
Demo: https://react-arborist.netlify.app/
|
|
4
8
|
|
|
5
9
|
The tree UI is ubiquitous in software applications. There are many tree component libraries for React, but none were full-featured enough to stand on their own.
|
|
6
10
|
|
|
@@ -24,37 +28,37 @@ Render the tree data structure.
|
|
|
24
28
|
|
|
25
29
|
```jsx
|
|
26
30
|
const data = {
|
|
27
|
-
id: "
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
id: "A",
|
|
32
|
+
name: "Root"
|
|
33
|
+
children: [
|
|
34
|
+
{ id: "B", name: "Node 1" },
|
|
35
|
+
{ id: "C", name: "Node 2" },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
30
38
|
|
|
31
39
|
function App() {
|
|
32
|
-
return
|
|
33
|
-
<Tree data={data}>
|
|
34
|
-
{Node}
|
|
35
|
-
</Tree>
|
|
36
|
-
);
|
|
40
|
+
return <Tree data={data}>{Node}</Tree>;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
function Node({ ref, styles, data}) {
|
|
43
|
+
function Node({ ref, styles, data }) {
|
|
40
44
|
return (
|
|
41
45
|
<div ref={ref} style={styles.row}>
|
|
42
|
-
<div style={styles.indent}>
|
|
43
|
-
{data.name}
|
|
44
|
-
</div>
|
|
46
|
+
<div style={styles.indent}>{data.name}</div>
|
|
45
47
|
</div>
|
|
46
|
-
)
|
|
48
|
+
);
|
|
47
49
|
}
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
#### Contents
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
|
|
54
|
+
- [Expected Data Structure](#expected-data-structure)
|
|
55
|
+
- [Tree Component API](#tree-component)
|
|
56
|
+
- [Node Renderer API](#node-renderer-component)
|
|
57
|
+
- [Styles Prop](#styles-prop)
|
|
58
|
+
- [State Prop](#state-prop)
|
|
59
|
+
- [Handlers Prop](#handlers-prop)
|
|
60
|
+
- [Tree Prop](#tree-prop)
|
|
61
|
+
- [Accessing the Tree Api from the Parent](#accessing-the-tree-api-from-the-parent)
|
|
58
62
|
|
|
59
63
|
## Expected Data Structure
|
|
60
64
|
|
|
@@ -65,36 +69,38 @@ type Data = {
|
|
|
65
69
|
id: string, /* Required */
|
|
66
70
|
children?: Data[]
|
|
67
71
|
isOpen?: boolean
|
|
72
|
+
...rest /* Any other data you wish to provide */
|
|
68
73
|
}
|
|
69
74
|
```
|
|
70
75
|
|
|
71
76
|
If your data does not look like this, you can provide a `childrenAccessor` prop. You can also provide `isOpenAccessor`. The value can be a string or a function.
|
|
72
77
|
|
|
73
78
|
```ts
|
|
74
|
-
<Tree childrenAccessor="items" ...
|
|
75
|
-
// Or
|
|
76
|
-
<Tree childrenAccessor={(node) => node.items} ...
|
|
79
|
+
<Tree childrenAccessor="items" ...
|
|
80
|
+
// Or
|
|
81
|
+
<Tree childrenAccessor={(node) => node.items} ...
|
|
77
82
|
```
|
|
78
83
|
|
|
79
84
|
## Tree Component
|
|
80
85
|
|
|
81
86
|
Unlike other Tree Components, react-arborist is designed as a [controlled component](https://reactjs.org/docs/forms.html#controlled-components). This means the consumer will provide the tree data and the handlers to update it. The only state managed internally is for drag and drop, selection, and editing.
|
|
82
87
|
|
|
83
|
-
| Prop
|
|
84
|
-
|
|
|
85
|
-
| data
|
|
86
|
-
| width
|
|
87
|
-
| height
|
|
88
|
-
| rowHeight
|
|
89
|
-
| indent
|
|
90
|
-
| hideRoot
|
|
91
|
-
| onToggle
|
|
92
|
-
| onMove
|
|
93
|
-
| onEdit
|
|
94
|
-
| childrenAccessor | "children" | Used to get a node's children if they exist on a property other than "children".
|
|
95
|
-
| isOpenAccessor
|
|
96
|
-
| openByDefault
|
|
97
|
-
| className
|
|
88
|
+
| Prop | Default | Description |
|
|
89
|
+
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
90
|
+
| data | (required) | The tree data structure to render as described above. |
|
|
91
|
+
| width | 300 | The width of the tree. |
|
|
92
|
+
| height | 500 | The height of the tree. To dynamically fill it's container, use a [hook](https://github.com/ZeeCoder/use-resize-observer) or [component](https://github.com/bvaughn/react-virtualized-auto-sizer) to gather the width and height of the Tree's parent. |
|
|
93
|
+
| rowHeight | 24 | The height of each row. |
|
|
94
|
+
| indent | 24 | The number of pixels to indent child nodes. |
|
|
95
|
+
| hideRoot | false | Hide the root node so that the first set of children appear as the roots. |
|
|
96
|
+
| onToggle | noop | Handler called when a node is opened or closed. This and the subsequent functions should update the `data` prop for the tree to re-render. |
|
|
97
|
+
| onMove | noop | Handler called when a user moves one or more nodes by dragging and dropping. |
|
|
98
|
+
| onEdit | noop | Handler called when a user performs an inline edit of the node. |
|
|
99
|
+
| childrenAccessor | "children" | Used to get a node's children if they exist on a property other than "children". |
|
|
100
|
+
| isOpenAccessor | "isOpen" | Used to get a node's openness state if it exists on a property other than "isOpen". |
|
|
101
|
+
| openByDefault | true | Choose if the node should be open or closed when it has an undefined openness state. |
|
|
102
|
+
| className | undefined | Adds a class to the containing div. |
|
|
103
|
+
| dndRootElement | undefined | The element for react-dnd to bind it's events to. Defaults to window. See https://github.com/brimdata/react-arborist/pull/33 |
|
|
98
104
|
|
|
99
105
|
The only child of the Tree Component must be a NodeRenderer function as described below.
|
|
100
106
|
|
|
@@ -103,7 +109,7 @@ const NodeRenderer = ({
|
|
|
103
109
|
innerRef, data, styles, handlers, state, tree
|
|
104
110
|
}) => ...
|
|
105
111
|
|
|
106
|
-
const MyApp = () =>
|
|
112
|
+
const MyApp = () =>
|
|
107
113
|
<Tree>
|
|
108
114
|
{NodeRenderer}
|
|
109
115
|
</Tree>
|
|
@@ -116,72 +122,64 @@ The Node Renderer is where you get to make the tree your own. You completely own
|
|
|
116
122
|
The most basic node renderer will look like this:
|
|
117
123
|
|
|
118
124
|
```jsx
|
|
119
|
-
function NodeRenderer({
|
|
120
|
-
innerRef,
|
|
121
|
-
styles,
|
|
122
|
-
data,
|
|
123
|
-
state,
|
|
124
|
-
handlers,
|
|
125
|
-
tree
|
|
126
|
-
}) {
|
|
125
|
+
function NodeRenderer({ innerRef, styles, data, state, handlers, tree }) {
|
|
127
126
|
return (
|
|
128
127
|
<div ref={innerRef} style={styles.row}>
|
|
129
128
|
<div style={styles.indent}>{data.id}</div>
|
|
130
129
|
</div>
|
|
131
|
-
)
|
|
130
|
+
);
|
|
132
131
|
}
|
|
133
132
|
```
|
|
134
133
|
|
|
135
134
|
The function above is passed data for this individual node, the DOM ref used for drag and drop, the styles to position the row in the virtualized list and the styles to indent the current node according to its level in the tree.
|
|
136
135
|
|
|
137
|
-
| Prop
|
|
138
|
-
|
|
|
139
|
-
| data
|
|
140
|
-
| innerRef
|
|
141
|
-
| [styles](#styles-prop)
|
|
142
|
-
| [state](#state-prop)
|
|
143
|
-
| [handlers](#handlers-prop) | object
|
|
144
|
-
| [tree](#tree-prop)
|
|
136
|
+
| Prop | Type | Description |
|
|
137
|
+
| -------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
138
|
+
| data | Node | A single node from the tree data structure provided. |
|
|
139
|
+
| innerRef | Ref | Must be attached to the root element returned by the NodeRenderer. This is needed for drag and drop to work. |
|
|
140
|
+
| [styles](#styles-prop) | object | This is an object that contains styles for the position of the row, and the level of indentation. Each key is described below. |
|
|
141
|
+
| [state](#state-prop) | object | An handful of boolean values that indicate the current state of this node. See below for details. |
|
|
142
|
+
| [handlers](#handlers-prop) | object | A collection of handlers to attach to the DOM, that provide selectable and toggle-able behaviors. Each handler is described below. |
|
|
143
|
+
| [tree](#tree-prop) | TreeMonitor | This object can be used to get at the internal state of the whole tree component. For example, `tree.getSelectedNodes()`. All the methods are listed below in the Tree Prop section. |
|
|
145
144
|
|
|
146
145
|
### Styles Prop
|
|
147
146
|
|
|
148
147
|
These are the properties on the styles object passed to the NodeRenderer.
|
|
149
148
|
|
|
150
|
-
| Name
|
|
151
|
-
|
|
|
152
|
-
| row
|
|
153
|
-
| indent | CSSProperties | This is simply a left padding set to the level of the tree multiplied by the tree indent prop.
|
|
149
|
+
| Name | Type | Description |
|
|
150
|
+
| ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| row | CSSProperties | Since the tree only renders the rows that are currently visible, all the rows are absolutely positioned with a fixed top and left. Those styles are in this property. |
|
|
152
|
+
| indent | CSSProperties | This is simply a left padding set to the level of the tree multiplied by the tree indent prop. |
|
|
154
153
|
|
|
155
154
|
### State Prop
|
|
156
155
|
|
|
157
156
|
These are the properties on the state object passed to the NodeRenderer.
|
|
158
157
|
|
|
159
|
-
| Name
|
|
160
|
-
|
|
|
161
|
-
| isOpen
|
|
162
|
-
| isSelected
|
|
163
|
-
| isHoveringOverChild | boolean | True if the user is dragging n node, and the node is hovering over one of this node's direct children. This can be used to indicate which folder the user is dragging an item into.
|
|
164
|
-
| isDragging
|
|
165
|
-
| isSelectedStart
|
|
166
|
-
| isSelectedEnd
|
|
167
|
-
| isEditing
|
|
168
|
-
|
|
158
|
+
| Name | Type | Description |
|
|
159
|
+
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
160
|
+
| isOpen | boolean | True if this node has children and the children are visible. Use this to display some type a open or closed icon. |
|
|
161
|
+
| isSelected | boolean | True if this node is selected. Use this to show a "selected" state. Maybe a different background? |
|
|
162
|
+
| isHoveringOverChild | boolean | True if the user is dragging n node, and the node is hovering over one of this node's direct children. This can be used to indicate which folder the user is dragging an item into. |
|
|
163
|
+
| isDragging | boolean | True if this node is being dragged. |
|
|
164
|
+
| isSelectedStart | boolean | True if this is the first of a contiguous group of selected rows. This can be used to tastefully style a group of selected items. Maybe a different border radius on the first and last rows? |
|
|
165
|
+
| isSelectedEnd | boolean | True if this is the last of a contiguous group of selected rows. |
|
|
166
|
+
| isEditing | boolean | True if this row is being edited. When true, the renderer should return some type of form input. |
|
|
169
167
|
|
|
170
168
|
### Handlers Prop
|
|
171
169
|
|
|
172
170
|
These are the properties on the handlers object passed to the NodeRenderer.
|
|
173
171
|
|
|
174
|
-
| Name
|
|
175
|
-
|
|
|
176
|
-
| select | MouseEventHandler
|
|
177
|
-
| toggle | MouseEventHandler
|
|
178
|
-
| edit
|
|
179
|
-
| submit | `(update: string) => void` | Sends the update to the `onEdit` handler in the Tree component, and sets the `state.isEditing` prop to `false`.
|
|
180
|
-
| reset
|
|
172
|
+
| Name | Type | Description |
|
|
173
|
+
| ------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| select | MouseEventHandler | Attach this to the element that tiggers selection. Maybe you want to add it to the outermost div. <br /> `<div onClick={handlers.select}>` |
|
|
175
|
+
| toggle | MouseEventHandler | Attach this to the element that opens and closes the node. Maybe you want to add it to the `+`/`-` icon. <br />`<icon onClick={handlers.toggle}>` |
|
|
176
|
+
| edit | `() => void` | Makes this node editable. This will re-render the Node with the `state.isEditing` prop set to `true`. |
|
|
177
|
+
| submit | `(update: string) => void` | Sends the update to the `onEdit` handler in the Tree component, and sets the `state.isEditing` prop to `false`. |
|
|
178
|
+
| reset | `() => void` | Re-renders with the `state.isEditing` prop set to `false`. |
|
|
181
179
|
|
|
182
180
|
### Tree Prop
|
|
183
181
|
|
|
184
|
-
The tree monitor provides methods to get the tree's internal state. A use case might be in a right click menu.
|
|
182
|
+
The tree monitor provides methods to get and change the tree's internal state. A use case might be in a right click menu.
|
|
185
183
|
|
|
186
184
|
```jsx
|
|
187
185
|
// In your node renderer
|
|
@@ -191,7 +189,32 @@ onContextMenu={() => {
|
|
|
191
189
|
}}
|
|
192
190
|
```
|
|
193
191
|
|
|
194
|
-
| Methods
|
|
195
|
-
|
|
|
196
|
-
| `getSelectedIds()`
|
|
197
|
-
| `edit(id: string)`
|
|
192
|
+
| Methods | Returns | Description |
|
|
193
|
+
| ----------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
194
|
+
| `getSelectedIds()` | `string[]` | Get the the ids of all currently selected nodes. |
|
|
195
|
+
| `edit(id: string)` | `Promise<{cancelled: boolean, value: string \| undefined}>` | Edit a node programatically. Resolves when the edit is finished or cancelled. |
|
|
196
|
+
| `submit(id: string, value: string)` | void | Submit the edit. |
|
|
197
|
+
| `reset(id: string)` | void | Cancel the edit. |
|
|
198
|
+
| `select(index: number, meta = false, shift = false)` | `void` | Select a node by it's visible index in the tree. The meta flag, when true, will allow multiple items to be selected. The shift flag, when true, will select all nodes between the last one selected, and this one. |
|
|
199
|
+
| `selectById(id: string, meta = false, shift = false)` | `void` | Same as above but selects by id. If the id is not present (in a collapsed folder), nothing will happen. First use the `scrollToId` function which will open all the parents and scroll to the id. |
|
|
200
|
+
| `scrollToId(id: string)` | void | Scroll to the id opening all it's parents if needed. |
|
|
201
|
+
|
|
202
|
+
[Full Tree API Implementation](https://github.com/brimdata/react-arborist/blob/main/packages/react-arborist/src/tree-api.ts)
|
|
203
|
+
|
|
204
|
+
### Accessing the Tree API from the Parent
|
|
205
|
+
|
|
206
|
+
You may need to access the tree from the parent component. This can be done by passing a ref.
|
|
207
|
+
|
|
208
|
+
```jsx
|
|
209
|
+
function MySection() {
|
|
210
|
+
const tree = useRef(null)
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
tree.current.scrollToId("B")
|
|
214
|
+
}, [])
|
|
215
|
+
|
|
216
|
+
return <Tree ref={ref} ... />
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Authored by [James Kerr](https://twitter.com/specialCaseDev)
|
package/dist/index.js
CHANGED
|
@@ -763,12 +763,12 @@ class $f02bc7cefcb30793$export$e2da3477247342d1 {
|
|
|
763
763
|
scrollToId(id) {
|
|
764
764
|
if (!this.list) return;
|
|
765
765
|
const index1 = this.idToIndex[id];
|
|
766
|
-
if (index1) this.list.scrollToItem(index1
|
|
766
|
+
if (index1) this.list.scrollToItem(index1);
|
|
767
767
|
else {
|
|
768
768
|
this.openParents(id);
|
|
769
769
|
($parcel$interopDefault($foSVk$reactdom)).flushSync(()=>{
|
|
770
770
|
const index = this.idToIndex[id];
|
|
771
|
-
if (index) this.list?.scrollToItem(index
|
|
771
|
+
if (index) this.list?.scrollToItem(index);
|
|
772
772
|
});
|
|
773
773
|
}
|
|
774
774
|
}
|
|
@@ -1283,8 +1283,8 @@ const $9a2860bfaab93091$export$b59bdbef9ce70de2 = /*#__PURE__*/ ($parcel$interop
|
|
|
1283
1283
|
const $641461e16d1a2941$var$OuterElement = /*#__PURE__*/ $foSVk$react.forwardRef(function Outer(props, ref) {
|
|
1284
1284
|
const { children: children , ...rest } = props;
|
|
1285
1285
|
const tree = $6723c76b9de38fd1$export$ea6c3ae2bd3a5510();
|
|
1286
|
-
return(
|
|
1287
|
-
|
|
1286
|
+
return(/*#__PURE__*/ $foSVk$reactjsxruntime.jsxs("div", {
|
|
1287
|
+
// @ts-ignore
|
|
1288
1288
|
ref: ref,
|
|
1289
1289
|
...rest,
|
|
1290
1290
|
onClick: tree.onClick,
|
|
@@ -1361,6 +1361,9 @@ const $641461e16d1a2941$export$7fbedc92909ed28e = /*#__PURE__*/ $foSVk$react.for
|
|
|
1361
1361
|
onContextMenu: props.onContextMenu,
|
|
1362
1362
|
children: /*#__PURE__*/ $foSVk$reactjsxruntime.jsxs($foSVk$reactdnd.DndProvider, {
|
|
1363
1363
|
backend: $foSVk$reactdndhtml5backend.HTML5Backend,
|
|
1364
|
+
options: {
|
|
1365
|
+
rootElement: props.dndRootElement || undefined
|
|
1366
|
+
},
|
|
1364
1367
|
children: [
|
|
1365
1368
|
/*#__PURE__*/ $foSVk$reactjsxruntime.jsx($641461e16d1a2941$var$OuterDrop, {
|
|
1366
1369
|
children: /*#__PURE__*/ $foSVk$reactjsxruntime.jsx($641461e16d1a2941$var$List, {
|