react-arborist 2.0.0-rc → 2.0.0-rc.2

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 ADDED
@@ -0,0 +1,714 @@
1
+ ![Logo](https://user-images.githubusercontent.com/3460638/161630636-3512fe81-41c2-4ee5-8f7e-adaad07033b6.svg)
2
+
3
+ <h1>React Arborist</h1>
4
+
5
+ [See the Demos](https://react-arborist.netlify.app/)
6
+
7
+ The tree view is ubiquitous in software applications. This library provides the React ecosystem with a complete solution to build the equivalent of a VSCode sidebar, Mac Finder, Windows Explorer, or Sketch/Figma layers panel.
8
+
9
+ Here is a Gmail sidebar clone built with react-arborist.
10
+
11
+ <img src="https://user-images.githubusercontent.com/3460638/197306119-59fe59e6-50ae-4bc2-8cb9-3faa2bc52cd2.gif" width="270px" alt="Gmail sidebar clone built with react-arborist" />
12
+
13
+ ## Features
14
+
15
+ - Drag and drop sorting
16
+ - Open/close folders
17
+ - Inline renaming
18
+ - Virtualized rendering
19
+ - Custom styling
20
+
21
+ **New Features in Version 2**
22
+
23
+ - Keyboard navigation
24
+ - Aria attributes
25
+ - Tree filtering
26
+ - Selection synchronization
27
+ - More callbacks (onScroll, onActivate, onSelect)
28
+ - Controlled or uncontrolled trees
29
+
30
+ > These docs are for version 2. It contains breaking changes. Here is the [v1.2.0 README](https://github.com/brimdata/react-arborist/tree/4fe9659d2c4cbd57582294330863d4fd7e7af74b).
31
+
32
+ ## Installation
33
+
34
+ ```
35
+ yarn add react-arborist
36
+ ```
37
+
38
+ ```
39
+ npm install react-arborist
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ Assume our data is this:
45
+
46
+ ```js
47
+ const data = [
48
+ { id: "1", name: "Unread" },
49
+ { id: "2", name: "Threads" },
50
+ {
51
+ id: "3",
52
+ name: "Chat Rooms",
53
+ children: [
54
+ { id: "c1", name: "General" },
55
+ { id: "c2", name: "Random" },
56
+ { id: "c3", name: "Open Source Projects" },
57
+ ],
58
+ },
59
+ {
60
+ id: "4",
61
+ name: "Direct Messages",
62
+ children: [
63
+ { id: "d1", name: "Alice" },
64
+ { id: "d2", name: "Bob" },
65
+ { id: "d3", name: "Charlie" },
66
+ ],
67
+ },
68
+ ];
69
+ ```
70
+
71
+ ### The Simplest Tree
72
+
73
+ Use all the defaults. The _initialData_ prop makes the tree an uncontrolled component. Create, move, rename, and delete will be handled internally.
74
+
75
+ ```jsx
76
+ function App() {
77
+ return <Tree initialData={data} />;
78
+ }
79
+ ```
80
+
81
+ ### Customize the Appearance
82
+
83
+ We provide our own dimensions and our own `Node` component.
84
+
85
+ ```jsx
86
+ function App() {
87
+ return (
88
+ <Tree
89
+ initialData={data}
90
+ openByDefault={false}
91
+ width={600}
92
+ height={1000}
93
+ indent={24}
94
+ rowHeight={36}
95
+ paddingTop={30}
96
+ paddingBottom={10}
97
+ padding={25 /* sets both */}
98
+ >
99
+ {Node}
100
+ </Tree>
101
+ );
102
+ }
103
+
104
+ function Node({ node, style, dragHandle }) {
105
+ /* This node instance can do many things. See the API reference. */
106
+ return (
107
+ <div style={style} ref={dragHandle}>
108
+ {node.isLeaf ? "🍁" : "🗀"}
109
+ {node.data.name}
110
+ </div>
111
+ );
112
+ }
113
+ ```
114
+
115
+ ### Control the Tree data
116
+
117
+ Here we use the _data_ prop to make the tree a controlled component. We must handle all the data modifications ourselves using the props below.
118
+
119
+ ```jsx
120
+ function App() {
121
+ /* Handle the data modifications outside the tree component */
122
+ const onCreate = ({ parentId, index, type }) => {};
123
+ const onRename = ({ id, name }) => {};
124
+ const onMove = ({ dragIds, parentId, index }) => {};
125
+ const onDelete = ({ ids }) => {};
126
+
127
+ return (
128
+ <Tree
129
+ data={data}
130
+ onCreate={onCreate}
131
+ onRename={onRename}
132
+ onMove={onMove}
133
+ onDelete={onDelete}
134
+ />
135
+ );
136
+ }
137
+ ```
138
+
139
+ ### Tree Filtering
140
+
141
+ Providing a non-empty _searchTerm_ will only show nodes that match. If a child matches, all its parents also match. Internal nodes are opened when filtering. You can provide your own _searchMatch_ function, or use the default.
142
+
143
+ ```jsx
144
+ function App() {
145
+ const term = useSearchTermString()
146
+ <Tree
147
+ data={data}
148
+ searchTerm={term}
149
+ searchMatch={
150
+ (node, term) => node.data.name.toLowerCase().includes(term.toLowerCase())
151
+ }
152
+ />
153
+ }
154
+ ```
155
+
156
+ ### Sync the Selection
157
+
158
+ It's common to open something elsewhere in the app, but have the tree reflect the new selection.
159
+
160
+ Passing an id to the _selection_ prop will select and scroll to that node whenever that id changes.
161
+
162
+ ```jsx
163
+ function App() {
164
+ const chatId = useCurrentChatId();
165
+
166
+ /*
167
+ Whenever the currentChatRoomId changes,
168
+ the tree will automatically select it and scroll to it.
169
+ */
170
+
171
+ return <Tree initialData={data} selection={chatId} />;
172
+ }
173
+ ```
174
+
175
+ ### Use the Tree Api Instance
176
+
177
+ You can access the Tree Api in the parent component by giving a ref to the tree.
178
+
179
+ ```jsx
180
+ function App() {
181
+ const treeRef = useRef();
182
+
183
+ useEffect(() => {
184
+ const tree = treeRef.current;
185
+ tree.selectAll();
186
+ /* See the Tree API reference for all you can do with it. */
187
+ }, []);
188
+
189
+ return <Tree initialData={data} ref={treeRef} />;
190
+ }
191
+ ```
192
+
193
+ ### Data with Different Property Names
194
+
195
+ The _idAccessor_ and _childrenAccessor_ props allow you to specify the children and id fields in your data.
196
+
197
+ ```jsx
198
+ function App() {
199
+ const data = [
200
+ {
201
+ category: "Food",
202
+ subCategories: [{ category: "Restaurants" }, { category: "Groceries" }],
203
+ },
204
+ ];
205
+ return (
206
+ <Tree
207
+ data={data}
208
+ /* An accessor can provide a string property name */
209
+ idAccessor="category"
210
+ /* or a function with the data as the argument */
211
+ childrenAccessor={(d) => d.subCategories}
212
+ />
213
+ );
214
+ }
215
+ ```
216
+
217
+ ### Custom Rendering
218
+
219
+ Render every single piece of the tree yourself. See the API reference for the props passed to each renderer.
220
+
221
+ ```jsx
222
+ function App() {
223
+ return (
224
+ <Tree
225
+ data={data}
226
+ /* The outer most element in the list */
227
+ renderRow={MyRow}
228
+ /* The "ghost" element that follows the mouse as you drag */
229
+ renderDragPreview={MyDragPreview}
230
+ /* The line that shows where an element will be dropped */
231
+ renderCursor={MyCursor}
232
+ >
233
+ {/* The inner element that shows the indentation and data */}
234
+ {MyNode}
235
+ </Tree>
236
+ );
237
+ }
238
+ ```
239
+
240
+ ## API Reference
241
+
242
+ - Components
243
+ - [Tree Component Props](#tree-component-props)
244
+ - [Row Component Props](#row-component-props)
245
+ - [Node Component Props](#node-component-props)
246
+ - [DragPreview Component Props](#dragpreview-component-props)
247
+ - [Cursor Component Props](#cursor-component-props)
248
+ - Interfaces
249
+ - [Node API](#node-api-reference)
250
+ - [Tree API](#tree-api-reference)
251
+
252
+ ## Tree Component Props
253
+
254
+ These are all the props you can pass to the Tree component.
255
+
256
+ ```ts
257
+ interface TreeProps<T extends IdObj> {
258
+ /* Data Options */
259
+ data?: T[];
260
+ initialData?: T[];
261
+
262
+ /* Data Handlers */
263
+ onCreate?: handlers.CreateHandler;
264
+ onMove?: handlers.MoveHandler;
265
+ onRename?: handlers.RenameHandler;
266
+ onDelete?: handlers.DeleteHandler;
267
+
268
+ /* Renderers*/
269
+ children?: ElementType<renderers.NodeRendererProps<T>>;
270
+ renderRow?: ElementType<renderers.RowRendererProps<T>>;
271
+ renderDragPreview?: ElementType<renderers.DragPreviewProps>;
272
+ renderCursor?: ElementType<renderers.CursorProps>;
273
+
274
+ /* Sizes */
275
+ rowHeight?: number;
276
+ width?: number;
277
+ height?: number;
278
+ indent?: number;
279
+ paddingTop?: number;
280
+ paddingBottom?: number;
281
+ padding?: number;
282
+
283
+ /* Config */
284
+ openByDefault?: boolean;
285
+ selectionFollowsFocus?: boolean;
286
+ disableDrag?: string | boolean | BoolFunc<T>;
287
+ disableDrop?: string | boolean | BoolFunc<T>;
288
+ childrenAccessor?: string | ((d: T) => T[]);
289
+ idAccessor?: string | ((d: T) => string);
290
+
291
+ /* Event Handlers */
292
+ onActivate?: (node: NodeApi<T>) => void;
293
+ onSelect?: (nodes: NodeApi<T>[]) => void;
294
+ onScroll?: (props: ListOnScrollProps) => void;
295
+ onToggle?: (id: string) => void;
296
+ onFocus?: (node: NodeApi<T>) => void;
297
+
298
+ /* Selection */
299
+ selection?: string;
300
+
301
+ /* Open State */
302
+ initialOpenState?: OpenMap;
303
+
304
+ /* Search */
305
+ searchTerm?: string;
306
+ searchMatch?: (node: NodeApi<T>, searchTerm: string) => boolean;
307
+
308
+ /* Extra */
309
+ className?: string | undefined;
310
+ rowClassName?: string | undefined;
311
+ dndRootElement?: globalThis.Node | null;
312
+ onClick?: MouseEventHandler;
313
+ onContextMenu?: MouseEventHandler;
314
+ }
315
+ ```
316
+
317
+ ## Row Component Props
318
+
319
+ The _\<RowRenderer\>_ is responsible for attaching the drop ref, the row style (top, height) and the aria-attributes. The default should work fine for most use cases, but it can be replaced by your own component if you need. See the _renderRow_ prop in the _\<Tree\>_ component.
320
+
321
+ ```ts
322
+ type RowRendererProps<T extends IdObj> = {
323
+ node: NodeApi<T>;
324
+ innerRef: (el: HTMLDivElement | null) => void;
325
+ attrs: HTMLAttributes<any>;
326
+ children: ReactElement;
327
+ };
328
+ ```
329
+
330
+ ## Node Component Props
331
+
332
+ The _\<NodeRenderer\>_ is responsible for attaching the drag ref, the node style (padding for indentation), the visual look of the node, the edit input of the node, and anything else you can dream up.
333
+
334
+ There is a default renderer, but it's only there as a placeholder to get started. You'll want to create your own component for this. It is passed as the _\<Tree\>_ components only child.
335
+
336
+ ```ts
337
+ export type NodeRendererProps<T extends IdObj> = {
338
+ style: CSSProperties;
339
+ node: NodeApi<T>;
340
+ tree: TreeApi<T>;
341
+ dragHandle?: (el: HTMLDivElement | null) => void;
342
+ preview?: boolean;
343
+ };
344
+ ```
345
+
346
+ ## DragPreview Component Props
347
+
348
+ The _\<DragPreview\>_ is responsible for showing a "ghost" version of the node being dragged. The default is a semi-transparent version of the NodeRenderer and should work fine for most people. To customize it, pass your new component to the _renderDragPreview_ prop.
349
+
350
+ ```ts
351
+ type DragPreviewProps = {
352
+ offset: XYCoord | null;
353
+ mouse: XYCoord | null;
354
+ id: string | null;
355
+ dragIds: string[];
356
+ isDragging: boolean;
357
+ };
358
+ ```
359
+
360
+ ## Cursor Component Props
361
+
362
+ The _\<Cursor\>_ is responsible for showing a line that indicates where the node will move to when it's dropped. The default is a blue line with circle on the left side. You may want to customize this. Pass your own component to the _renderCursor_ prop.
363
+
364
+ ```ts
365
+ export type CursorProps = {
366
+ top: number;
367
+ left: number;
368
+ indent: number;
369
+ };
370
+ ```
371
+
372
+ ## Node API Reference
373
+
374
+ ### State Properties
375
+
376
+ All these properties on the node instance return booleans related to the state of the node.
377
+
378
+ _node_.**isRoot**
379
+
380
+ Returns true if this is the root node. The root node is added internally by react-arborist and not shown in the UI.
381
+
382
+ _node_.**isLeaf**
383
+
384
+ Returns true if the children property is not an array.
385
+
386
+ _node_.**isInternal**
387
+
388
+ Returns true if the children property is an array.
389
+
390
+ _node_.**isOpen**
391
+
392
+ Returns true if node is internal and in an open state.
393
+
394
+ _node_.**isEditing**
395
+
396
+ Returns true if this node is currently being edited. Use this property in the NodeRenderer to render the rename form.
397
+
398
+ _node_.**isSelected**
399
+
400
+ Returns true if node is selected.
401
+
402
+ _node_.**isSelectedStart**
403
+
404
+ Returns true if node is the first of a contiguous group of selected nodes. Useful for styling.
405
+
406
+ _node_.**isSelectedEnd**
407
+
408
+ Returns true if node is the last of a contiguous group of selected nodes. Useful for styling.
409
+
410
+ _node_.**isOnlySelection**
411
+
412
+ Returns true if node is the only node selected in the tree.
413
+
414
+ _node_.**isFocused**
415
+
416
+ Returns true if node is focused.
417
+
418
+ _node_.**isDragging**
419
+
420
+ Returns true if node is being dragged.
421
+
422
+ _node_.**willReceiveDrop**
423
+
424
+ Returns true if node is internal and the user is hovering a dragged node over it.
425
+
426
+ _node_.**state**
427
+
428
+ Returns an object with all the above properties as keys and boolean values. Useful for adding class names to an element with a library like [clsx](https://github.com/lukeed/clsx) or [classnames](https://github.com/JedWatson/classnames).
429
+
430
+ ```ts
431
+ type NodeState = {
432
+ isEditing: boolean;
433
+ isDragging: boolean;
434
+ isSelected: boolean;
435
+ isSelectedStart: boolean;
436
+ isSelectedEnd: boolean;
437
+ isFocused: boolean;
438
+ isOpen: boolean;
439
+ isClosed: boolean;
440
+ isLeaf: boolean;
441
+ isInternal: boolean;
442
+ willReceiveDrop: boolean;
443
+ };
444
+ ```
445
+
446
+ ### Accessors
447
+
448
+ _node_.**childIndex**
449
+
450
+ Returns the node's index in relation to its siblings.
451
+
452
+ _node_.**next**
453
+
454
+ Returns the next visible node. The node directly under this node in the tree component. Returns null if none exist.
455
+
456
+ _node_.**prev**
457
+
458
+ Returns the previous visible node. The node directly above this node in the tree component. Returns null if none exist.
459
+
460
+ _node_.**nextSibling**
461
+
462
+ Returns the next sibling in the data of this node. Returns null if none exist.
463
+
464
+ ### Selection Methods
465
+
466
+ _node_.**select**()
467
+
468
+ Select only this node.
469
+
470
+ _node_.**deselect**()
471
+
472
+ Deselect this node. Other nodes may still be selected.
473
+
474
+ _node_.**selectMulti**()
475
+
476
+ Select this node while maintaining all other selections.
477
+
478
+ _node_.**selectContiguous**()
479
+
480
+ Deselect all nodes from the anchor node to the last selected node, the select all nodes from the anchor node to this node. The anchor changes to the focused node after calling _select()_ or _selectMulti()_.
481
+
482
+ ### Activation Methods
483
+
484
+ _node_.**activate**()
485
+
486
+ Runs the Tree props' onActivate callback passing in this node.
487
+
488
+ _node_.**focus**()
489
+
490
+ Focus this node.
491
+
492
+ ### Open/Close Methods
493
+
494
+ _node_.**open**()
495
+
496
+ Opens the node if it is an internal node.
497
+
498
+ _node_.**close**()
499
+
500
+ Closes the node if it is an internal node.
501
+
502
+ _node_.**toggle**()
503
+
504
+ Toggles the open/closed state of the node if it is an internal node.
505
+
506
+ _node_.**openParents**()
507
+
508
+ Opens all the parents of this node.
509
+
510
+ _node_.**edit**()
511
+
512
+ Moves this node into the editing state. Calling node._isEditing_ will return true.
513
+
514
+ _node_.**submit**(_newName_)
515
+
516
+ Submits _newName_ string to the _onRename_ handler. Moves this node out of the editing state.
517
+
518
+ _node_.**reset**()
519
+
520
+ Moves this node out of the editing state without submitting a new name.
521
+
522
+ ### Event Handlers
523
+
524
+ _node_.**handleClick**(_event_)
525
+
526
+ Useful for using the standard selection methods when a node is clicked. If the meta key is down, call _multiSelect()_. If the shift key is down, call _selectContiguous()_. Otherwise, call _select()_ and _activate()_.
527
+
528
+ ## Tree API Reference
529
+
530
+ The tree api reference is stable across re-renders. It always has the most recent state and props.
531
+
532
+ ### Node Accessors
533
+
534
+ _tree_.**get**(_id_) : _NodeApi | null_
535
+
536
+ Get node by id from the _visibleNodes_ array.
537
+
538
+ _tree_.**at**(_index_) : _NodeApi | null_
539
+
540
+ Get node by index from the _visibleNodes_ array.
541
+
542
+ _tree_.**visibleNodes** : _NodeApi[]_
543
+
544
+ Returns an array of the visible nodes.
545
+
546
+ _tree_.**firstNode** : _NodeApi | null_
547
+
548
+ The first node in the _visibleNodes_ array.
549
+
550
+ _tree_.**lastNode** : _NodeApi | null_
551
+
552
+ The last node in the _visibleNodes_ array.
553
+
554
+ _tree_.**focusedNode** : _NodeApi | null_
555
+
556
+ The currently focused node.
557
+
558
+ _tree_.**mostRecentNode** : _NodeApi | null_
559
+
560
+ The most recently selected node.
561
+
562
+ _tree_.**nextNode** : _NodeApi | null_
563
+
564
+ The node directly after the _focusedNode_ in the _visibleNodes_ array.
565
+
566
+ _tree_.**prevNode** : _NodeApi | null_
567
+
568
+ The node directly before the _focusedNode_ in the _visibleNodes_ array.
569
+
570
+ ### Focus Methods
571
+
572
+ _tree_.**hasFocus** : _boolean_
573
+
574
+ Returns true if the the tree has focus somewhere within it.
575
+
576
+ _tree_.**focus**(_id_)
577
+
578
+ Focus on the node with _id_.
579
+
580
+ _tree_.**isFocused**(_id_) : _boolean_
581
+
582
+ Check if the node with _id_ is focused.
583
+
584
+ _tree_.**pageUp**()
585
+
586
+ Move focus up one page.
587
+
588
+ _tree_.**pageDown**()
589
+
590
+ Move focus down one page.
591
+
592
+ ### Selection Methods
593
+
594
+ _tree_.**selectedIds** : _Set\<string\>_
595
+
596
+ Returns a set of ids that are selected.
597
+
598
+ _tree_.**selectedNodes** : _NodeApi[]_
599
+
600
+ Returns an array of nodes that are selected.
601
+
602
+ _tree_.**hasNoSelection** : boolean
603
+
604
+ Returns true if nothing is selected in the tree.
605
+
606
+ _tree_.**hasSingleSelection** : boolean
607
+
608
+ Returns true if there is only one selection.
609
+
610
+ _tree_.**hasMultipleSelections** : boolean
611
+
612
+ Returns true if there is more than one selection.
613
+
614
+ _tree_.**isSelected**(_id_) : _boolean_
615
+
616
+ Returns true if the node with _id_ is selected.
617
+
618
+ _tree_.**select**(_id_)
619
+
620
+ Select only the node with _id_.
621
+
622
+ _tree_.**deselect**(_id_)
623
+
624
+ Deselect the node with _id_.
625
+
626
+ _tree_.**selectMulti**(_id_)
627
+
628
+ Add to the selection the node with _id_.
629
+
630
+ _tree_.**selectContiguous**(_id_)
631
+
632
+ Deselected nodes between the anchor and the last selected node, then select the nodes between the anchor and the node with _id_.
633
+
634
+ _tree_.**deselectAll**()
635
+
636
+ Deselect all nodes.
637
+
638
+ _tree_.**selectAll**()
639
+
640
+ Select all nodes.
641
+
642
+ ### Visibility
643
+
644
+ _tree_.**open**(_id_)
645
+
646
+ Open the node with _id_.
647
+
648
+ _tree_.**close**(_id_)
649
+
650
+ Close the node with _id_.
651
+
652
+ _tree_.**toggle**(_id_)
653
+
654
+ Toggle the open state of the node with _id_.
655
+
656
+ _tree_.**openParents**(_id_)
657
+
658
+ Open all parents of the node with _id_.
659
+
660
+ _tree_.**openSiblings**(_id_)
661
+
662
+ Open all siblings of the node with _id_.
663
+
664
+ _tree_.**openAll**()
665
+
666
+ Open all internal nodes.
667
+
668
+ _tree_.**closeAll**()
669
+
670
+ Close all internal nodes.
671
+
672
+ _tree_.**isOpen**(_id_) : _boolean_
673
+
674
+ Returns true if the node with _id_ is open.
675
+
676
+ ### Drag and Drop
677
+
678
+ _tree_.**isDragging**(_id_) : _boolean_
679
+
680
+ Returns true if the node with _id_ is being dragged.
681
+
682
+ _tree_.**willReceiveDrop**(_id_) : _boolean_
683
+
684
+ Returns true if the node with _id_ is internal and is under the dragged node.
685
+
686
+ ### Scrolling
687
+
688
+ _tree_.**scrollTo**(_id_, _[align]_)
689
+
690
+ Scroll to the node with _id_. If this node is not visible, this method will open all its parents. The align argument can be _"auto" | "smart" | "center" | "end" | "start"_.
691
+
692
+ ### Properties
693
+
694
+ _tree_.**isEditing** : _boolean_
695
+
696
+ Returns true if the tree is editing a node.
697
+
698
+ _tree_.**isFiltered** : _boolean_
699
+
700
+ Returns true if the _searchTerm_ prop is not an empty string when trimmed.
701
+
702
+ _tree_.**props** : _TreeProps_
703
+
704
+ Returns all the props that were passed to the _\<Tree\>_ component.
705
+
706
+ _tree_.**root** : _NodeApi_
707
+
708
+ Returns the root _NodeApi_ instance. Its children are the Node representations of the _data_ prop array.
709
+
710
+ ## Author
711
+
712
+ This library was created by James Kerr while working at Brim Data on the [Zui desktop app](https://www.youtube.com/watch?v=I2y663n8d2A). Work with data? Check us out at [brimdata.io](https://www.brimdata.io)
713
+
714
+ [Follow me on Twitter](https://twitter.com/specialCaseDev) for react-arborist updates.
@@ -1,2 +1,2 @@
1
1
  /// <reference types="react" />
2
- export declare function DropCursor(): JSX.Element | null;
2
+ export declare function Cursor(): JSX.Element | null;
@@ -1,2 +1,7 @@
1
1
  /// <reference types="react" />
2
+ /**
3
+ * All these keyboard shortcuts seem like they should be configurable.
4
+ * Each operation should be a given a name and separated from
5
+ * the event handler. Future clean up welcome.
6
+ */
2
7
  export declare function DefaultContainer(): JSX.Element;