react-arborist 2.0.0-rc.1 → 2.0.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 CHANGED
@@ -2,9 +2,13 @@
2
2
 
3
3
  <h1>React Arborist</h1>
4
4
 
5
- The tree view is ubiquitous in software applications. This library provides the React ecosystem with complete solution to build the equivalent of the VSCode sidebar, the Mac Finder, the Windows Explorer, or the Sketch/Figma layers panel.
5
+ [See the Demos](https://react-arborist.netlify.app/)
6
6
 
7
- _New Link To Demo_
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" />
8
12
 
9
13
  ## Features
10
14
 
@@ -23,8 +27,6 @@ _New Link To Demo_
23
27
  - More callbacks (onScroll, onActivate, onSelect)
24
28
  - Controlled or uncontrolled trees
25
29
 
26
- _NEW DEMO GIF HERE_
27
-
28
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).
29
31
 
30
32
  ## Installation
@@ -76,6 +78,10 @@ function App() {
76
78
  }
77
79
  ```
78
80
 
81
+ <img width="214" alt="image" src="https://user-images.githubusercontent.com/3460638/198098015-d7dc6400-6391-4094-9f66-0f56a99433e9.png">
82
+
83
+ [Demo](https://codesandbox.io/s/the-simplest-tree-7tbedw)
84
+
79
85
  ### Customize the Appearance
80
86
 
81
87
  We provide our own dimensions and our own `Node` component.
@@ -110,6 +116,10 @@ function Node({ node, style, dragHandle }) {
110
116
  }
111
117
  ```
112
118
 
119
+ <img width="166" alt="image" src="https://user-images.githubusercontent.com/3460638/198100281-594a492d-2ea0-4ff0-883d-1dd79dbb5acd.png">
120
+
121
+ [Demo](https://codesandbox.io/s/customize-appearance-f4g15v?file=/src/App.tsx)
122
+
113
123
  ### Control the Tree data
114
124
 
115
125
  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.
@@ -267,8 +277,7 @@ interface TreeProps<T extends IdObj> {
267
277
  children?: ElementType<renderers.NodeRendererProps<T>>;
268
278
  renderRow?: ElementType<renderers.RowRendererProps<T>>;
269
279
  renderDragPreview?: ElementType<renderers.DragPreviewProps>;
270
- renderCursor?: ElementType<renderers.DropCursorProps>;
271
- renderContainer?: ElementType<{}>;
280
+ renderCursor?: ElementType<renderers.CursorProps>;
272
281
 
273
282
  /* Sizes */
274
283
  rowHeight?: number;
@@ -291,6 +300,8 @@ interface TreeProps<T extends IdObj> {
291
300
  onActivate?: (node: NodeApi<T>) => void;
292
301
  onSelect?: (nodes: NodeApi<T>[]) => void;
293
302
  onScroll?: (props: ListOnScrollProps) => void;
303
+ onToggle?: (id: string) => void;
304
+ onFocus?: (node: NodeApi<T>) => void;
294
305
 
295
306
  /* Selection */
296
307
  selection?: string;
@@ -304,6 +315,7 @@ interface TreeProps<T extends IdObj> {
304
315
 
305
316
  /* Extra */
306
317
  className?: string | undefined;
318
+ rowClassName?: string | undefined;
307
319
  dndRootElement?: globalThis.Node | null;
308
320
  onClick?: MouseEventHandler;
309
321
  onContextMenu?: MouseEventHandler;
@@ -327,7 +339,7 @@ type RowRendererProps<T extends IdObj> = {
327
339
 
328
340
  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.
329
341
 
330
- There is a default renderer, but it's only there as a placeholder to get started. You'll wan't to create your own component for this. It is passed as the _\<Tree\>_ components only child.
342
+ 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.
331
343
 
332
344
  ```ts
333
345
  export type NodeRendererProps<T extends IdObj> = {
@@ -358,7 +370,7 @@ type DragPreviewProps = {
358
370
  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.
359
371
 
360
372
  ```ts
361
- export type DropCursorProps = {
373
+ export type CursorProps = {
362
374
  top: number;
363
375
  left: number;
364
376
  indent: number;
@@ -367,7 +379,7 @@ export type DropCursorProps = {
367
379
 
368
380
  ## Node API Reference
369
381
 
370
- #### State Properties
382
+ ### State Properties
371
383
 
372
384
  All these properties on the node instance return booleans related to the state of the node.
373
385
 
@@ -403,6 +415,10 @@ _node_.**isSelectedEnd**
403
415
 
404
416
  Returns true if node is the last of a contiguous group of selected nodes. Useful for styling.
405
417
 
418
+ _node_.**isOnlySelection**
419
+
420
+ Returns true if node is the only node selected in the tree.
421
+
406
422
  _node_.**isFocused**
407
423
 
408
424
  Returns true if node is focused.
@@ -428,11 +444,14 @@ type NodeState = {
428
444
  isSelectedEnd: boolean;
429
445
  isFocused: boolean;
430
446
  isOpen: boolean;
447
+ isClosed: boolean;
448
+ isLeaf: boolean;
449
+ isInternal: boolean;
431
450
  willReceiveDrop: boolean;
432
451
  };
433
452
  ```
434
453
 
435
- #### Accessors
454
+ ### Accessors
436
455
 
437
456
  _node_.**childIndex**
438
457
 
@@ -450,7 +469,7 @@ _node_.**nextSibling**
450
469
 
451
470
  Returns the next sibling in the data of this node. Returns null if none exist.
452
471
 
453
- #### Selection Methods
472
+ ### Selection Methods
454
473
 
455
474
  _node_.**select**()
456
475
 
@@ -468,7 +487,7 @@ _node_.**selectContiguous**()
468
487
 
469
488
  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()_.
470
489
 
471
- #### Activation Methods
490
+ ### Activation Methods
472
491
 
473
492
  _node_.**activate**()
474
493
 
@@ -478,7 +497,7 @@ _node_.**focus**()
478
497
 
479
498
  Focus this node.
480
499
 
481
- #### Open/Close Methods
500
+ ### Open/Close Methods
482
501
 
483
502
  _node_.**open**()
484
503
 
@@ -508,7 +527,7 @@ _node_.**reset**()
508
527
 
509
528
  Moves this node out of the editing state without submitting a new name.
510
529
 
511
- #### Event Handlers
530
+ ### Event Handlers
512
531
 
513
532
  _node_.**handleClick**(_event_)
514
533
 
@@ -518,7 +537,7 @@ Useful for using the standard selection methods when a node is clicked. If the m
518
537
 
519
538
  The tree api reference is stable across re-renders. It always has the most recent state and props.
520
539
 
521
- #### Node Accessors
540
+ ### Node Accessors
522
541
 
523
542
  _tree_.**get**(_id_) : _NodeApi | null_
524
543
 
@@ -556,7 +575,7 @@ _tree_.**prevNode** : _NodeApi | null_
556
575
 
557
576
  The node directly before the _focusedNode_ in the _visibleNodes_ array.
558
577
 
559
- #### Focus Methods
578
+ ### Focus Methods
560
579
 
561
580
  _tree_.**hasFocus** : _boolean_
562
581
 
@@ -578,7 +597,7 @@ _tree_.**pageDown**()
578
597
 
579
598
  Move focus down one page.
580
599
 
581
- #### Selection Methods
600
+ ### Selection Methods
582
601
 
583
602
  _tree_.**selectedIds** : _Set\<string\>_
584
603
 
@@ -588,6 +607,18 @@ _tree_.**selectedNodes** : _NodeApi[]_
588
607
 
589
608
  Returns an array of nodes that are selected.
590
609
 
610
+ _tree_.**hasNoSelection** : boolean
611
+
612
+ Returns true if nothing is selected in the tree.
613
+
614
+ _tree_.**hasSingleSelection** : boolean
615
+
616
+ Returns true if there is only one selection.
617
+
618
+ _tree_.**hasMultipleSelections** : boolean
619
+
620
+ Returns true if there is more than one selection.
621
+
591
622
  _tree_.**isSelected**(_id_) : _boolean_
592
623
 
593
624
  Returns true if the node with _id_ is selected.
@@ -616,7 +647,7 @@ _tree_.**selectAll**()
616
647
 
617
648
  Select all nodes.
618
649
 
619
- #### Visibility
650
+ ### Visibility
620
651
 
621
652
  _tree_.**open**(_id_)
622
653
 
@@ -638,11 +669,19 @@ _tree_.**openSiblings**(_id_)
638
669
 
639
670
  Open all siblings of the node with _id_.
640
671
 
672
+ _tree_.**openAll**()
673
+
674
+ Open all internal nodes.
675
+
676
+ _tree_.**closeAll**()
677
+
678
+ Close all internal nodes.
679
+
641
680
  _tree_.**isOpen**(_id_) : _boolean_
642
681
 
643
682
  Returns true if the node with _id_ is open.
644
683
 
645
- #### Drag and Drop
684
+ ### Drag and Drop
646
685
 
647
686
  _tree_.**isDragging**(_id_) : _boolean_
648
687
 
@@ -652,13 +691,13 @@ _tree_.**willReceiveDrop**(_id_) : _boolean_
652
691
 
653
692
  Returns true if the node with _id_ is internal and is under the dragged node.
654
693
 
655
- #### Scrolling
694
+ ### Scrolling
656
695
 
657
696
  _tree_.**scrollTo**(_id_, _[align]_)
658
697
 
659
698
  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"_.
660
699
 
661
- #### Properties
700
+ ### Properties
662
701
 
663
702
  _tree_.**isEditing** : _boolean_
664
703
 
@@ -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,3 +1,3 @@
1
1
  import React from "react";
2
- import { DropCursorProps } from "../types/renderers";
3
- export declare const DefaultCursor: React.NamedExoticComponent<DropCursorProps>;
2
+ import { CursorProps } from "../types/renderers";
3
+ export declare const DefaultCursor: React.NamedExoticComponent<CursorProps>;
package/dist/index.js CHANGED
@@ -72,6 +72,7 @@ $parcel$export($eb5355379510ac9b$exports, "isDecendent", () => $eb5355379510ac9b
72
72
  $parcel$export($eb5355379510ac9b$exports, "indexOf", () => $eb5355379510ac9b$export$305f7d4e9d4624f2);
73
73
  $parcel$export($eb5355379510ac9b$exports, "noop", () => $eb5355379510ac9b$export$8793edee2d425525);
74
74
  $parcel$export($eb5355379510ac9b$exports, "dfs", () => $eb5355379510ac9b$export$51b654aff22fc5a6);
75
+ $parcel$export($eb5355379510ac9b$exports, "walk", () => $eb5355379510ac9b$export$588732934346abbf);
75
76
  $parcel$export($eb5355379510ac9b$exports, "focusNextElement", () => $eb5355379510ac9b$export$3b0237e8566c8d65);
76
77
  $parcel$export($eb5355379510ac9b$exports, "focusPrevElement", () => $eb5355379510ac9b$export$33b47db07a82b2fb);
77
78
  $parcel$export($eb5355379510ac9b$exports, "access", () => $eb5355379510ac9b$export$9bb0e144ba4929ca);
@@ -80,6 +81,8 @@ $parcel$export($eb5355379510ac9b$exports, "identify", () => $eb5355379510ac9b$ex
80
81
  $parcel$export($eb5355379510ac9b$exports, "mergeRefs", () => $eb5355379510ac9b$export$c9058316764c140e);
81
82
  $parcel$export($eb5355379510ac9b$exports, "safeRun", () => $eb5355379510ac9b$export$c6d63370cef03886);
82
83
  $parcel$export($eb5355379510ac9b$exports, "waitFor", () => $eb5355379510ac9b$export$9bbfceb27f687c1b);
84
+ $parcel$export($eb5355379510ac9b$exports, "getInsertIndex", () => $eb5355379510ac9b$export$e12bf2314d0bc2a9);
85
+ $parcel$export($eb5355379510ac9b$exports, "getInsertParentId", () => $eb5355379510ac9b$export$58fe32731f07ed56);
83
86
  function $eb5355379510ac9b$export$adf7c0fe6059d774(n, min, max) {
84
87
  return Math.max(Math.min(n, max), min);
85
88
  }
@@ -111,6 +114,10 @@ function $eb5355379510ac9b$export$51b654aff22fc5a6(node, id) {
111
114
  }
112
115
  return null;
113
116
  }
117
+ function $eb5355379510ac9b$export$588732934346abbf(node, fn) {
118
+ fn(node);
119
+ if (node.children) for (let child of node.children)$eb5355379510ac9b$export$588732934346abbf(child, fn);
120
+ }
114
121
  function $eb5355379510ac9b$export$3b0237e8566c8d65(target) {
115
122
  const elements = $eb5355379510ac9b$var$getFocusable(target);
116
123
  let next;
@@ -183,6 +190,20 @@ function $eb5355379510ac9b$export$9bbfceb27f687c1b(fn) {
183
190
  check();
184
191
  });
185
192
  }
193
+ function $eb5355379510ac9b$export$e12bf2314d0bc2a9(tree) {
194
+ const focus = tree.focusedNode;
195
+ if (!focus) return tree.root.children?.length ?? 0;
196
+ if (focus.isOpen) return 0;
197
+ if (focus.parent) return focus.childIndex + 1;
198
+ return 0;
199
+ }
200
+ function $eb5355379510ac9b$export$58fe32731f07ed56(tree) {
201
+ const focus = tree.focusedNode;
202
+ if (!focus) return null;
203
+ if (focus.isOpen) return focus.id;
204
+ if (focus.parent) return focus.parent.id;
205
+ return null;
206
+ }
186
207
 
187
208
 
188
209
 
@@ -368,12 +389,18 @@ class $9b37fe5960a1a3c6$export$d4b903da0f522dc8 {
368
389
  get isOpen() {
369
390
  return this.isLeaf ? false : this.tree.isOpen(this.id);
370
391
  }
392
+ get isClosed() {
393
+ return this.isLeaf ? false : !this.tree.isOpen(this.id);
394
+ }
371
395
  get isEditing() {
372
396
  return this.tree.editingId === this.id;
373
397
  }
374
398
  get isSelected() {
375
399
  return this.tree.isSelected(this.id);
376
400
  }
401
+ get isOnlySelection() {
402
+ return this.isSelected && this.tree.hasOneSelection;
403
+ }
377
404
  get isSelectedStart() {
378
405
  return this.isSelected && !this.prev?.isSelected;
379
406
  }
@@ -391,13 +418,16 @@ class $9b37fe5960a1a3c6$export$d4b903da0f522dc8 {
391
418
  }
392
419
  get state() {
393
420
  return {
394
- isEditing: this.isEditing,
421
+ isClosed: this.isClosed,
395
422
  isDragging: this.isDragging,
396
- isSelected: this.isSelected,
397
- isSelectedStart: this.isSelectedStart,
398
- isSelectedEnd: this.isSelectedEnd,
423
+ isEditing: this.isEditing,
399
424
  isFocused: this.isFocused,
425
+ isInternal: this.isInternal,
426
+ isLeaf: this.isLeaf,
400
427
  isOpen: this.isOpen,
428
+ isSelected: this.isSelected,
429
+ isSelectedEnd: this.isSelectedEnd,
430
+ isSelectedStart: this.isSelectedStart,
401
431
  willReceiveDrop: this.willReceiveDrop
402
432
  };
403
433
  }
@@ -833,7 +863,7 @@ const $6e3db8c23c41dfc9$var$PreviewNode = /*#__PURE__*/ (0, $foSVk$react.memo)(f
833
863
 
834
864
 
835
865
 
836
- function $e527b4e3d64e6932$export$ef961593063b03e8() {
866
+ function $e527b4e3d64e6932$export$b6a79797ad180576() {
837
867
  const tree = (0, $d5cb84d44d1b8acc$export$367b0f2231a90ba0)();
838
868
  const state = (0, $d5cb84d44d1b8acc$export$4930f6bf413be70e)();
839
869
  const cursor = state.cursor;
@@ -876,10 +906,7 @@ const $0e2adc7837d85ac3$var$DropContainer = ()=>{
876
906
  left: "0",
877
907
  right: "0"
878
908
  },
879
- onClick: (e)=>{
880
- console.log(e.currentTarget, e.target);
881
- },
882
- children: /*#__PURE__*/ (0, $foSVk$reactjsxruntime.jsx)((0, $e527b4e3d64e6932$export$ef961593063b03e8), {})
909
+ children: /*#__PURE__*/ (0, $foSVk$reactjsxruntime.jsx)((0, $e527b4e3d64e6932$export$b6a79797ad180576), {})
883
910
  });
884
911
  };
885
912
 
@@ -1228,7 +1255,8 @@ const $37e3f46f8fd1101f$export$a9754b3c8daa5172 = /*#__PURE__*/ (0, ($parcel$int
1228
1255
  "aria-level": node.level,
1229
1256
  "aria-selected": node.isSelected,
1230
1257
  style: rowStyle,
1231
- tabIndex: -1
1258
+ tabIndex: -1,
1259
+ className: tree.props.rowClassName
1232
1260
  };
1233
1261
  (0, $foSVk$react.useEffect)(()=>{
1234
1262
  if (!node.isEditing && node.isFocused) el.current?.focus();
@@ -1530,16 +1558,16 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1530
1558
  return this.state.nodes.open.unfiltered;
1531
1559
  }
1532
1560
  /* Tree Props */ get width() {
1533
- return this.props.width || 300;
1561
+ return this.props.width ?? 300;
1534
1562
  }
1535
1563
  get height() {
1536
- return this.props.height || 500;
1564
+ return this.props.height ?? 500;
1537
1565
  }
1538
1566
  get indent() {
1539
- return this.props.indent || 24;
1567
+ return this.props.indent ?? 24;
1540
1568
  }
1541
1569
  get rowHeight() {
1542
- return this.props.rowHeight || 24;
1570
+ return this.props.rowHeight ?? 24;
1543
1571
  }
1544
1572
  get searchTerm() {
1545
1573
  return (this.props.searchTerm || "").trim();
@@ -1609,31 +1637,20 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1609
1637
  return this.state.nodes.edit.id;
1610
1638
  }
1611
1639
  createInternal() {
1612
- return this.create("internal");
1640
+ return this.create({
1641
+ type: "internal"
1642
+ });
1613
1643
  }
1614
1644
  createLeaf() {
1615
- return this.create("leaf");
1616
- }
1617
- async create(type) {
1618
- let index;
1619
- let parentId;
1620
- const focus = this.focusedNode;
1621
- if (focus && focus.parent) {
1622
- if (focus.isInternal && focus.isOpen) {
1623
- parentId = focus.id;
1624
- index = 0;
1625
- } else {
1626
- index = focus.childIndex + 1;
1627
- parentId = focus.parent.isRoot ? null : focus.parent.id;
1628
- }
1629
- } else {
1630
- index = this.root?.children?.length || -1;
1631
- parentId = null;
1632
- }
1645
+ return this.create({
1646
+ type: "leaf"
1647
+ });
1648
+ }
1649
+ async create(opts = {}) {
1633
1650
  const data = await $5c74fef433be2b0a$var$safeRun(this.props.onCreate, {
1634
- parentId: parentId,
1635
- index: index,
1636
- type: type
1651
+ type: opts.type ?? "leaf",
1652
+ parentId: opts.parentId === undefined ? $eb5355379510ac9b$exports.getInsertParentId(this) : opts.parentId,
1653
+ index: opts.index ?? $eb5355379510ac9b$exports.getInsertIndex(this)
1637
1654
  });
1638
1655
  if (data) {
1639
1656
  this.focus(data);
@@ -1716,6 +1733,7 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1716
1733
  else {
1717
1734
  this.dispatch((0, $61ef7f2c3c9633e7$export$d7ddd398f22d79ef)($5c74fef433be2b0a$var$identify(node)));
1718
1735
  if (opts.scroll !== false) this.scrollTo(node);
1736
+ if (this.focusedNode) $5c74fef433be2b0a$var$safeRun(this.props.onFocus, this.focusedNode);
1719
1737
  }
1720
1738
  }
1721
1739
  pageUp() {
@@ -1744,6 +1762,7 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1744
1762
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).anchor(id));
1745
1763
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).mostRecent(id));
1746
1764
  this.scrollTo(id, opts.align);
1765
+ if (this.focusedNode) $5c74fef433be2b0a$var$safeRun(this.props.onFocus, this.focusedNode);
1747
1766
  $5c74fef433be2b0a$var$safeRun(this.props.onSelect, this.selectedNodes);
1748
1767
  }
1749
1768
  deselect(node) {
@@ -1759,6 +1778,7 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1759
1778
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).anchor(node.id));
1760
1779
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).mostRecent(node.id));
1761
1780
  this.scrollTo(node);
1781
+ if (this.focusedNode) $5c74fef433be2b0a$var$safeRun(this.props.onFocus, this.focusedNode);
1762
1782
  $5c74fef433be2b0a$var$safeRun(this.props.onSelect, this.selectedNodes);
1763
1783
  }
1764
1784
  selectContiguous(identity) {
@@ -1770,6 +1790,7 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1770
1790
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).add(this.nodesBetween(anchor, $5c74fef433be2b0a$var$identifyNull(id))));
1771
1791
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).mostRecent(id));
1772
1792
  this.scrollTo(id);
1793
+ if (this.focusedNode) $5c74fef433be2b0a$var$safeRun(this.props.onFocus, this.focusedNode);
1773
1794
  $5c74fef433be2b0a$var$safeRun(this.props.onSelect, this.selectedNodes);
1774
1795
  }
1775
1796
  deselectAll() {
@@ -1783,6 +1804,7 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1783
1804
  this.dispatch((0, $61ef7f2c3c9633e7$export$d7ddd398f22d79ef)(this.lastNode?.id));
1784
1805
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).anchor(this.firstNode));
1785
1806
  this.dispatch((0, $58f9381615aa3d17$export$e324594224ef24da).mostRecent(this.lastNode));
1807
+ if (this.focusedNode) $5c74fef433be2b0a$var$safeRun(this.props.onFocus, this.focusedNode);
1786
1808
  $5c74fef433be2b0a$var$safeRun(this.props.onSelect, this.selectedNodes);
1787
1809
  }
1788
1810
  /* Drag and Drop */ get cursorParentId() {
@@ -1808,12 +1830,16 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1808
1830
  /* Visibility */ open(identity) {
1809
1831
  const id = $5c74fef433be2b0a$var$identifyNull(identity);
1810
1832
  if (!id) return;
1833
+ if (this.isOpen(id)) return;
1811
1834
  this.dispatch((0, $d519ceb3313d9d0e$export$e324594224ef24da).open(id, this.isFiltered));
1835
+ $5c74fef433be2b0a$var$safeRun(this.props.onToggle, id);
1812
1836
  }
1813
1837
  close(identity) {
1814
1838
  const id = $5c74fef433be2b0a$var$identifyNull(identity);
1815
1839
  if (!id) return;
1840
+ if (!this.isOpen(id)) return;
1816
1841
  this.dispatch((0, $d519ceb3313d9d0e$export$e324594224ef24da).close(id, this.isFiltered));
1842
+ $5c74fef433be2b0a$var$safeRun(this.props.onToggle, id);
1817
1843
  }
1818
1844
  toggle(identity) {
1819
1845
  const id = $5c74fef433be2b0a$var$identifyNull(identity);
@@ -1839,6 +1865,16 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1839
1865
  this.scrollTo(this.focusedNode);
1840
1866
  }
1841
1867
  }
1868
+ openAll() {
1869
+ $eb5355379510ac9b$exports.walk(this.root, (node)=>{
1870
+ if (node.isInternal) node.open();
1871
+ });
1872
+ }
1873
+ closeAll() {
1874
+ $eb5355379510ac9b$exports.walk(this.root, (node)=>{
1875
+ if (node.isInternal) node.close();
1876
+ });
1877
+ }
1842
1878
  /* Scrolling */ scrollTo(identity, align = "smart") {
1843
1879
  if (!identity) return;
1844
1880
  const id = $5c74fef433be2b0a$var$identify(identity);
@@ -1860,6 +1896,15 @@ class $5c74fef433be2b0a$export$e2da3477247342d1 {
1860
1896
  get hasFocus() {
1861
1897
  return this.state.nodes.focus.treeFocused;
1862
1898
  }
1899
+ get hasNoSelection() {
1900
+ return this.state.nodes.selection.ids.size === 0;
1901
+ }
1902
+ get hasOneSelection() {
1903
+ return this.state.nodes.selection.ids.size === 1;
1904
+ }
1905
+ get hasMultipleSelections() {
1906
+ return this.state.nodes.selection.ids.size > 1;
1907
+ }
1863
1908
  isSelected(id) {
1864
1909
  if (!id) return false;
1865
1910
  return this.state.nodes.selection.ids.has(id);
@@ -2033,6 +2078,7 @@ function $9511ad6af37da13b$export$c49dab5eb1b4ce0c({ treeProps: treeProps , impe
2033
2078
 
2034
2079
 
2035
2080
 
2081
+
2036
2082
  function $6c0a9a91d5e7ff45$export$5a6c424b1725f44f() {
2037
2083
  const tree = (0, $d5cb84d44d1b8acc$export$367b0f2231a90ba0)();
2038
2084
  // In case we drop an item at the bottom of the list
@@ -2042,9 +2088,28 @@ function $6c0a9a91d5e7ff45$export$5a6c424b1725f44f() {
2042
2088
  if (!m.isOver({
2043
2089
  shallow: true
2044
2090
  })) return;
2091
+ if (m.canDrop()) {
2092
+ const offset = m.getClientOffset();
2093
+ if (!tree.listEl.current || !offset) return;
2094
+ const { cursor: cursor } = (0, $462841de7cc5b715$export$f502ca02ebb85a1c)({
2095
+ element: tree.listEl.current,
2096
+ offset: offset,
2097
+ indent: tree.indent,
2098
+ node: null,
2099
+ prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
2100
+ nextNode: null
2101
+ });
2102
+ if (cursor) tree.showCursor(cursor);
2103
+ } else tree.hideCursor();
2104
+ },
2105
+ canDrop: (item, m)=>{
2106
+ if (!m.isOver({
2107
+ shallow: true
2108
+ })) return false;
2109
+ if (tree.isFiltered) return false;
2045
2110
  const offset = m.getClientOffset();
2046
- if (!tree.listEl.current || !offset) return;
2047
- const { cursor: cursor } = (0, $462841de7cc5b715$export$f502ca02ebb85a1c)({
2111
+ if (!tree.listEl.current || !offset) return false;
2112
+ const { drop: drop } = (0, $462841de7cc5b715$export$f502ca02ebb85a1c)({
2048
2113
  element: tree.listEl.current,
2049
2114
  offset: offset,
2050
2115
  indent: tree.indent,
@@ -2052,12 +2117,15 @@ function $6c0a9a91d5e7ff45$export$5a6c424b1725f44f() {
2052
2117
  prevNode: tree.visibleNodes[tree.visibleNodes.length - 1],
2053
2118
  nextNode: null
2054
2119
  });
2055
- if (cursor) tree.showCursor(cursor);
2056
- },
2057
- canDrop: (item, m)=>{
2058
- return m.isOver({
2059
- shallow: true
2060
- });
2120
+ if (!drop) return false;
2121
+ const dropParent = tree.get(drop.parentId) ?? tree.root;
2122
+ for (let id of item.dragIds){
2123
+ const drag = tree.get(id);
2124
+ if (!drag) return false;
2125
+ if (!dropParent) return false;
2126
+ if (drag.isInternal && (0, $eb5355379510ac9b$export$1e38f72c6c546f70)(dropParent, drag)) return false;
2127
+ }
2128
+ return true;
2061
2129
  },
2062
2130
  drop: (item, m)=>{
2063
2131
  if (m.didDrop()) return;