GithubHelp home page GithubHelp logo

lukasbach / react-complex-tree Goto Github PK

View Code? Open in Web Editor NEW
761.0 4.0 65.0 7.55 MB

Unopinionated Accessible Tree Component with Multi-Select and Drag-And-Drop

Home Page: https://rct.lukasbach.com

License: MIT License

JavaScript 5.24% TypeScript 66.47% CSS 3.76% MDX 24.53%
react component tree accessible dnd drag-and-drop search rename keyboard hotkeys

react-complex-tree's Introduction

React Complex Tree

npm downloads

Demo for React Complex Tree

An Unopinionated Accessible Tree Component with Multi-Select and Drag-And-Drop

React Complex Tree - Unopinionated accessible tree component with drag and drop | Product Hunt

Look into the official documentation to see more examples and a more comprehensive documentation. Many common issues or questions are covered in the FAQ page.

The Changelog is available at https://rct.lukasbach.com/docs/changelog. Check the v2 release notes when migrating from v1.x to v2.x.

Sponsors

The development of react-complex-tree is supported by the community. Special thanks to:



Find out how to support the development of react-complex-tree.

Installation

To start using React Complex Tree, install it to your project as a dependency via

npm install react-complex-tree
yarn add react-complex-tree

then import it and add your tree structure with

import { UncontrolledTreeEnvironment, Tree, StaticTreeDataProvider } from 'react-complex-tree';

<UncontrolledTreeEnvironment
  dataProvider={new StaticTreeDataProvider(longTree.items, (item, data) => ({ ...item, data }))}
  getItemTitle={item => item.data}
  viewState={{}}
>
  <Tree treeId="tree-1" rootItem="root" treeLabel="Tree Example" />
</UncontrolledTreeEnvironment>;

More details at the Get-Started Guide. The guide on how to integrate data with a static tree data provider is also a good starting point to understand how to integrate data with React Complex Tree.

Features

  • Unopinionated

    React Complex Tree does not make any assumptions about any aesthetics of your web design or any technologies that you are using. The rendering is entirely up to you, and every node written to DOM can be customized. Sensible defaults styled by easily customizable CSS classes are provided to ease integration.

  • Accessible

    The tree structure conforms to W3C's specification for accessible trees. It supports screen readers and implements all common keyboard interactions so that every interaction, from moving the focus to dragging items, is possible without using the mouse.

  • Powerful Drag and Drop

    The tree provides the expected capabilities that power users expect from advanced tooling. Select as many items as you want, and drag them at any location within the same or any other tree! React Complex Tree comes with many customization options for Drag and Drop, such as disallowing reordering or enabling dragging or dropping on certain items only.

  • Full Keyboard Controls

    The tree is entirely controllable via keyboard. It implements all controls suggested by the W3C to make trees accessible, and provides further controls for Drag and Drop, searching or renaming items.

  • Zero Dependencies

    We know how annoying it is to add a package and end up with hundreds of peer dependencies. Because React Complex Tree does not make any assumptions on your any dependencies, we also do not need to clutter your project with further packages. When adding React Complex Tree to your package, you add only that and no other dependencies.

  • Multi-Selection

    Other than other more simple tree libraries, React Complex Tree allows you to select as many items as you want, and move them all at once by dragging to a different location. Why provide your users with less functionality, when you can settle with powerful tree capabilities with no additional effort? Try it out on the demo above and select multiple items at once by holding control on your keyboard while clicking on items, then dragging all at once to a different location.

  • Renaming built in

    React Complex Tree provides renaming as native feature with its capabilities. Select any item and press F2, to start renaming the item. This provides a more intuitive way of renaming items for users without implementing custom dialog solutions that are more disruptive to your users workflow.

  • Search Functionality

    Have you ever tried to find that one file in an enormous chaotic file tree that you know is there, but have no idea where? Just start typing while focusing the tree, and the first item matching your search will show up. This also improves accessibility for the tree as keyboard-only users can more easily navigate the tree structure.

  • Multi-Tree Environments

    You can use several trees on your web app that share a common state, and are able to interact with one another. The state and tree items are provided to a common react provider component, and as many trees as you want can easily be integrated by just adding tree components below the provider. The trees do not need to provide their own state, they just need an ID and their root item, all other logic is handled by the provider.

  • Controlled and Uncontrolled interfaces

    The most easiest way of using React Complex Tree is using an uncontrolled tree environment that maintains the tree state, i.e. which items are selected, expanded, etc. itself. You only need to supply a data provider that defines how items are asynchronously loaded, and the environment does the rest. However, if you want more control, you can instead use the controlled environment for full customizable.

  • Powered by React and TypeScript

    React Complex Tree is powered by React (duh) and is easily integrated in existing React projects by just importing and using the provided components. Comprehensive type information is given as TypeScript interfaces, that ease the integration and provide additional type safety, no matter whether you use TypeScript in your project or not.

Hints for contributing

If you want to develop RCT locally, here are some hints:

  • Use volta to make sure you have a compatible NodeJS and Yarn version installed
  • Run yarn to install dependencies
  • Run yarn build once locally before running any dev commands
  • Run yarn start to start docusaurus and the package in watch mode, and/or yarn storybook to run storybook
  • Make sure to run linter and tests before pushing changes

react-complex-tree's People

Contributors

alexdarling avatar creativoma avatar cyphergurke avatar dependabot[bot] avatar dlech avatar kherp avatar lukasbach avatar minhir avatar paulrostorp avatar ron-deepdub avatar sameersismail avatar sebastianfdz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-complex-tree's Issues

Coping items

First of all, awesome project!

I wanted to ask if it is possible to copy item with CTRL+c from one group and insert it to another group with CTRL+v? That both groups would have this particular item as a child. Or add an option if node is moved or duplicated while dragging.

Do not hide root node and allow multiple root nodes.

I'm not sure the current implementation is fully in line with the the spec (I realize this is a bit of a gray area).

Currently, a tree like this is generated (only showing role attributes):

<div role="tree">
  <ul role="group">
    <li role="treeitem">
      ...
      <ul role="group">
        <li role="treeitem">...</li>
        <li role="treeitem">...</li>
        <li role="treeitem">...</li>
        ...
      </ul>
    </li>
    <li role="treeitem">...</li>
    <li role="treeitem">...</li>
    ...
  </ul>
</div>

The spec says:

  • Each element serving as a tree node has role treeitem.
    ...
  • Each parent node contains or owns an element with role group.

However, the root node lacks the role of "treeitem".

Also, the example implementation also does not have a top-level node with the role of "group".

The spec also says:

Each root node is contained in the element with role tree or referenced by an aria-owns property set on the tree element.

Which implies that multiple root nodes are expected.

Drag & drop doesn't work properly when an item is hidden

Describe the bug
When you don't render an item (or hide it) included in parent's children array (e.g. "Meals" in "root"), it still is recognized during drag and drop (issue doesn't exist e.g. on click), which makes all following elements invalid.
You can check the bug on codesandbox

To Reproduce
Apply display: none; to the element or render null/fragment/empty element instead of it.

Expected behavior
On drop the hidden/not rendered item should be completely ignored. In my sandbox example: when I drag "Fruit" and drop on "Desserts", I should get "Desserts" in console or on preview, but I get "Meals". Basically everything after "Fruit" (or "Banana" if "Fruit" is expanded), becomes an item before including hidden/not rendered items.

Desktop (please complete the following information):

  • OS: Windows
  • Browser: Microsoft Edge
  • Version: 103.0.1264.71

Additional context
In my project, I use 2 trees and they can contain any amount of items. I need to implement search functionality, but I can't use built-in due to the possible huge amount of items. Only visible items are added to the data object, but I still need to search for all items (I have API for that) and since I need drag & drop between them, I have to use 1 Controlled Environment so I thought the best idea would be to hide items that aren't in searched items' paths, but that won't work due to the issue.

ControlledTreeEnvironment and drag & drop

I don't know if it's a bug or a misunderstanding.
Does drag and drop work in ControlledTreeEnvironment mode?

I have no problem with UncontrolledTreeEnvironment, but after anothers tests with ControlledTreeEnvironment, the Drag&Drop do not work, and I couldn't find the answer in the documentation.

I reroduced the problem in codesandbox :
https://codesandbox.io/s/react-complex-controlledtreeenvironment-drag-ng6mme

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser: Latest Chrome and Firefox
  • Version: latest

Thanks for this excellent lib ;)

Drag and Drop on ControlledTreeEnvironment doesn't work

Describe the bug
If in a ControlledTreeEnvironment the drag and drop wont work

To Reproduce
https://codesandbox.io/s/ng6mme?file=/src/App.tsx

Expected behavior
Expecting item to be dropped, it is possible to drag it.
Desktop (please complete the following information):

  • OS: W10
  • Browser: chrome
  • Version latest

Additional context

With
canDragAndDrop={true} canReorderItems={true} canDropOnItemWithChildren={true} canDropOnItemWithoutChildren={true}
And no restrictions it is not possible to drop the item, the canDropAt seems to work fine but also doesn't allow me to drop the item

Possibility to use the same default classes in custom renderers

the current context passed to renderItem={({ title, arrow, depth, context, children }) does not contain the related active CSS classes that apply to the button according to the state change.

Would it be possible to get an example of how to reproduce the same exact output as in the default complex tree, using a renderItem override?

Multiselect doesn't work on MacOS

Latest version 1.1.11 doesn't correctly recognize command button click. The bug was fixed by downgrading to 1.1.10. What is odd is that if I use current state of the repo by copy/paste of core package, then it works. This means that it is not as much for the change of the code, as much as how the bundler(in my case CreateReactApp) is using the new version.

As additional info, it happens on ControlledTreeEnvironment component.

It is very much re-occurrence of #80

Non-contiguous multi-selection doesn't work with `cmd` + click on Mac

Describe the bug
On a Mac, attempting to select multiple nodes while holding down cmd + clicking does not work. It seems that the Windows ctrl key may be mapped to the mac control key rather than the expected cmd key.

Holding down control + click on a Mac performs a right-click on the system level which would overpower the tree's hotkey if it is indeed using control + click.

The only way I can select a non-contiguous set of nodes on Mac is via the keyboard when I do control + space.

To Reproduce
Attempt to cmd + click to perform a non-contiguous multi-select while using a Mac.

Expected behavior
cmd + click should have the same effect as control + space currently does

Screenshots
Here's attempting to do this via mouse. First is holding down cmd while clicking, second is holding control while clicking

Screen.Recording.2022-06-02.at.7.26.28.PM.mov

Desktop (please complete the following information):

  • OS: macOS Monterey
  • Browser: Chromium
  • Version: 102

Dragging a unselected node calls canDrag() before selecting it and canDrag receives the seleciton

Describe the bug
Before calling canDrag(), the lib computes itemsToDrag.

const itemsToDrag =
viewState?.selectedItems?.map(item => environment.items[item]) ??
(viewState?.focusedItem ? [environment.items[viewState?.focusedItem]] : []);

When a node is selected and I start dragging another node, the selection is not updated immediately. canDrag() receive the selected node instead of the dragged node.

Tree inside form triggers window reload on click

Describe the bug
When the tree is a child of a form element, and the user clicks on any node on the tree, then the window refreshes

To Reproduce
Add a tree inside a form element, and click on any node on the tree. The window will refresh

Expected behavior
We expect that the primary action will trigger (and maybe expand) but without refreshing the window

Demo
https://codesandbox.io/s/react-complex-tree-playground-forked-r69m1?file=/src/App.tsx

Desktop:

  • OS: iOS
  • Browser: Chrome
  • Version: Latest, 94

Root cause
The container element is a button by default, and the default button type is submit. When a submit button is clicked inside a form it automatically refreshes the window, unless someone calls preventDefault on the form on submit event or the on click event of the button.

Solution
Add an attribute type="button" to the button node container:

I can raise a PR if needed

Thanks for the great component btw!

extensible key bindings

It would be useful to be able to add additional key bindings to trees. For example I would like to implement a delete action when the delete key is pressed that receives the focused item and additional state.

It could also be useful to allow 3rd party libraries to implement their own keybindings handling instead of using the built-in one here. This is useful when the 3rd-party library is used to display all keybinding in an app and/or allow the user to customize the key bindings.

unpkg bunlde - standalone package errors

I am trying to use the unpkg bundle as mentioned in the documentation but I get an error when the library loads.

Here's a simple html file loading the required libraries, please check the console for the error

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>RCT</title>
  <script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
  <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/react-complex-tree/lib/bundle.js"></script>
</head>
<body>
  Check if rct works
</body>
</html>

Error in web console:

InteractionManagerProvider.tsx:29 Uncaught TypeError: a.createContext is not a function
    at Object.<anonymous> (InteractionManagerProvider.tsx:29:39)
    at n (bootstrap:19:22)
    at Object.<anonymous> (ControlledTreeEnvironment.tsx:36:36)
    at n (bootstrap:19:22)
    at Object.<anonymous> (index.ts:13:14)
    at n (bootstrap:19:22)
    at bootstrap:83:10
    at bundle.js:1:1204
    at universalModuleDefinition:9:30
    at universalModuleDefinition:1:1

Is this a build issue? Has this been tested?

Library doesn't handle well drag'n'drop on big trees

Describe the bug
If controlled tree has so many children, that some of the children are outside of the viewport, then the drop target is not correct. I guess this is expected limitation, but I wanted you to be aware of it.

I am not 100% sure, but I think that the same goes for uncontrolled tree as well.

To Reproduce
Steps to reproduce the behavior:

Create tree with many children(lets say 50-100) that it requires scrolling to see them all. Then try to trigger drag'n'drop event that includes scrolling the list. You will notice that drop target is wrong element. It seems that logic to track the drop target get confused for some reason.

  • OS: Manjaro
  • Browser - tested on all major browsers
  • Version - "react-complex-tree": "1.1.4"

I used the library as it was the only well-supported React.js library for tree-view that supports drag'n'drop. Big thanks for that, as I really enjoyed the TS definitions and the documentation.

problem renaming items

Describe the bug
Abort and submit rename operations seems to be broken

my situation:

  • using controlled tree component
  • implementing this functions:
 onStartRenamingItem={(item, name) =>
        console.log(`${item.data} being renamed to ${name}`)
      }
      onRenameItem={(item, name) => {
        console.log(`${item.data} renamed to ${name}`);
        handleRename(item, name);
      }}
      onAbortRenamingItem={(item, name) =>
        console.log(`${item.data} aborted to ${name}`)
      }
  • output:

image

two problems here:

  1. clicking on the renaming-submit-button does not actually trigger the "onRenameItem" callback
  2. pressing "escape" key while renaming item does not trigger onAbortRenamingItem
  3. clicking outside of the input text field while renaming item does not trigger onAbortRenamingItem

blueprintjs renderer crashes because of undefined Icons.ChevronRight

I tried adding the renderers as suggested in the docs to a sandbox and it crashes:

https://codesandbox.io/s/react-complex-tree-playground-forked-cs3i6k?file=/src/App.tsx

Error

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of TreeItem.

Originally posted by @dlech in #38 (comment)

extensible view state

It would be useful to be able to add addition custom state to the viewState property of a ControlledTreeEnvironment.

One use case is for having an "active" item separate from the focused item. This state is usually indicated by making the title <strong> and/or highlighting the background.

Another use case could be a list of disabled items. These are usually rendered with the title color muted and don't respond to keyboard/mouse input.

Blueprint renderers don't work with ClickArrowToExpand

Describe the bug
When using ClickArrowToExpand and the blueprint renderers, nothing happens when I click the arrow.

To Reproduce
Steps to reproduce the behavior:

  • use the blueprint renderers
  • use ClickArrowToExpand
  • click the arrow to expand
  • observe that it doesn't expand

Expected behavior
The node should expand when clicking on its arrow.

How to add a new node to the tree?

Hello Team,

I just stumbled upon this library. Could you please share more details on how to add,edit, delete nodes and children?

Thanks,
Arun

Escaping during drag still performs action

Describe the bug
I have 2 trees, in which one item can be dragged onto the other one. When I am doing this, if I change my mind and press the escape key, the drag animation stops and the item is properly returned to the original tree, but the onDrop event still happens on the last focused item of the target tree.

This actually happens even within the same tree.

To Reproduce
Steps to reproduce the behavior:

Create 2 trees and follow the bug description

Expected behavior
When I press escape, the dragged item should return back to the original tree, and the onDrop event should not occur on the target.

Can't move cursor by arrow keys while renaming

The arrow keys were bound to expand or collapse items by default and it looks not to be considered to skip the listener while renaming. Can we add a condition !tree.isRenaming here?

  useKey(
    'arrowright',
    e => {
    },
    isActiveTree && !dnd.isProgrammaticallyDragging && !tree.isRenaming
  );

  useKey(
    'arrowleft',
    e => {
    },
    isActiveTree && !dnd.isProgrammaticallyDragging && !tree.isRenaming
  );

Video showing bug alongside Syncfusion's ContextMenuComponent

Included you will find a zip file containing my example project as well as a video. Please watch the video as I will reference what is going on below.

When the tree control is paired with Syncfusion's ContextMenuComponent, react seems to remove some essential html elements that the ContextMenuComponent requires. As soon as I remove the tree control from the test, everything works as expected.

When the video starts, I expect the context menu to show whenever the vertical dots in the tree control are clicked OR the OPEN div is clicked.

29 sec: I make a change to a comment so I can quick save/hot reload, showing console errors to help pinpoint what may be going on. Even though some of the errors point to the ContextMenuComponent, I believe the root cause is that the tree control is removing some of the elements it needs to operate.

57 sec: I comment out the tree control and show that the context menu shows when the OPEN div is clicked as it should.

1:30 sec: I show an essential DOM element created and utilized by the context menu control 'e-contextmenu-wrapper'. When that control is present, there usually isn't an issue.

1:55 sec: When the tree view control is brought back in AND the page is refreshed, the 'e-contextmenu-wrapper' is no longer preset, meaning the context menu will no longer work.
[complex-tree-issue.zip]

(https://github.com/lukasbach/react-complex-tree/files/9316674/complex-tree-issue.zip)

renaming from context of custom renderer is not working

Describe the bug
I am using a custom renderer for items and It has a menu dropdown, from where the user can click on the rename option to rename the item, but the context.startRenaming method doesn't work at all.

here is the renderer

renderItem: ({ item, depth, children, title, context, arrow }) => {

    let _startRenaming = ()=> {

      console.log(context, "... _startRenaming")
      context.startRenamingItem(); // <= this method is empty and make no effect
    }
}

also, I see that this function is not implemented yet, this might be the reason

To Reproduce

  1. try to rename from the context of renderItem renderer

Expected behavior
The item should be renamed programmatically from the context

Desktop (please complete the following information):

  • OS: macOS
  • Browser chrome, safari
  • Version 1.1.8

Mobile drag-and-drop support

Hey, really great library, thanks for the work!

As the selection and drag events differ on mobile and desktop, DnD behavior currently doesn't work on mobile. Is this on the roadmap? (Or how much work would it be to implement?)

I think the mappings are:dragstart is touchstart; drag, dragenter, dragover, dragleave is touchmove; drop, dragend is touchend.

Keyboard navigation is out-of-sync from viewstate

Describe the bug
A clear and concise description of what the bug is.

when opening a folder in the tree using the right-arrow, and then move down, the focus is on the item after the tree item, IF the 'items' of the tree are the same. when the items change as well (for instance items keeping track of expand state as well) then it works fine.

To Reproduce
Steps to reproduce the behavior:

  1. visit: https://rct.lukasbach.com/docs/guides/controlled-environment/#managing-the-view-state-of-the-tree
  2. Click 'Desserts' (it should expand immediately)
  3. Press arrow down (focus becomes "Drinks")

Expected behavior
A clear and concise description of what you expected to happen.

  1. visit: https://rct.lukasbach.com/docs/guides/controlled-environment/#managing-the-view-state-of-the-tree
  2. Click 'Desserts' (it should expand immediately)
  3. Press arrow down (focus should become "Cookies")

Screenshots
If applicable, add screenshots to help explain your problem.

keyboard-bug

Desktop (please complete the following information):

  • OS: MacOS
  • Browser: Chrome
  • Version: 102

Additional context
Add any other context about the problem here.

I could fix this behaviour in my own application by updating the object reference of items whenever the expanded view state changed. I suspect there the keyboard navigation is somehow using an out of date 'linear list' because it only updates when the 'items' change, and not when the 'expandedItems' state changes.

multiple selection and single selection configurable

Is your feature request related to a problem? Please describe.
yes, read below description

Describe the solution you'd like
In my case I only need a single selection of Item, I never found a way to prevent multiple item selection. If there is a config/props to prevent multi-selection then it would give more control to manage tree item.

Describe alternatives you've considered
there is no alternative

Important Bug with ControlledEnvironment and Drag&Drop.

Discussed in #104

Originally posted by gpisano97 July 19, 2022
Hello, i'm using your component on React,js (17). I'm working with a big tree (with many nested children), and (with my team mates) we implemented a Controlled Environment (because we need to add and remove items), but the drag and drop logic is broken. We have filled the onDrop event and the "putting an item inside another" mechanism works. If we don't expand any Item also the reordering mechanism works, buf if we expand some Items and we try to move some child became all broken! The "target" element is wrong and in some cases (with the elements on bottom of the tree) also the "item" array is wrong!!! On "bestofreactjs.com" there is the issue #15, and seems to be similar. We are in trouble, this problems is making lose a big amount of time! How can we fix it ? Thanks all for the answers.

Best Regards.

blueprintjs rendered keyboard navigation is broken

Describe the bug
Keyboard navigation doesn't work as expected when using the blueprintjs renderer.

When the tree gets focus by tabbing with the keyboard, pressing down and up just scrolls the page instead of navigating the tree.

If you select a node with the mouse first, then navigation sort of works, but it seems to get stuck on expandable nodes.

To Reproduce
Steps to reproduce the behavior:

Go to https://rct.lukasbach.com/docs/guides/blueprintjs and try to use keyboard navigation.

Expected behavior

The keyboard navigation should work as in other examples.

Desktop (please complete the following information):

  • OS: Ubuntu 20.04
  • Browser Firefox
  • Version 98

Drop doesn't work unless cursor is *exactly* on top of `renderDragBetweenLine`

Describe the bug
When dropping a between-items event. The cursor must stay exactly on top of renderDragBetweenLine, or else the drop won't work. Even though the line is shown

This is visible when dragging under the last element of a tree

It's even possible to see the cursor show the + symbol when exactly on top of renderDragBetweenLine but not otherwise

To Reproduce
Steps to reproduce the behavior:

  • Drag an item under the last element of a tree

Expected behavior
Either allow the user to drop when cursor is not on top of renderDragBetweenLine, which drops at the last place the line was rendered. Or hide renderDragBetweenLine.

Screenshots
CleanShot 2022-09-06 at 14 51 25

Desktop (please complete the following information):

  • OS: macOS Monterey 12.4
  • Browser: Chrome 105.0.5195.102
  • Version: 1.1.6

Support dynamic data provider

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Dropping into self

Describe the bug
You can drag an item into it self if it has a child that accepts drop.

https://p195.p4.n0.cdn.getcloudapp.com/items/geud4ggR/d26a0b2d-f670-4252-8347-ef92a302968b.gif?v=907ccf16be512a91e317756cf823cf47

Expected
Disallow this sort of thing.

canDropAt={(items, target) => {

// prevent self drop
if(items[0].index == target.parentItem) return false

            // check if target is a child of parent
            const isSelf = itemsSource[items[0].index].children.findIndex(i => i == target.parentItem)
            if(isSelf !== -1) return false
          
return true
 } 
}

That is some basic code to check. If the fist item is the same as the a parent. I also check if parentItem is a child of the first item checking the items. (I called it itemsSource).

This isn't great and doesn't check for multiple selects. But it kind of works. :)

Changelog?

Thanks for a great repo!
Could we please have some form of changelog? I couldn't find either changelog or version.md or history.md or descriptions in the releases themselves.

default interaction mode disable all default events

with reference: https://rct.lukasbach.com/docs/guides/interaction-modes

Describe the bug
custom interaction mode disable all default events

To Reproduce
include the following prop to a ControlledComponent

 defaultInteractionMode={{
          mode: "custom",
          createInteractiveElementProps: (
            item,
            treeId,
            actions,
            renderFlags
          ) => ({
            onDoubleClick: () => {
              actions.focusItem();
              actions.selectItem();
              treeRef?.current?.startRenamingItem(item.index);
            },
          }),
        }}

I can achieve the intended behavior, i.e. that the list item goes into renaming mode on doubleClik, but all other events are not handled anymore, like onClick, etc.

DnD doesn't work when other component is rendered using `react-dnd`

Describe the bug
The drag and drop functionality is being stopped when any other component is rendered with react-dnd implementation

Expected behavior
The DnD functionality would be working regardless of other dnd lib usage.

Desktop (please complete the following information):

  • OS: Ubuntu
  • Browser: chrome
  • Version 1.1.11

Error when removing nodes

Describe the bug
I have implemented the following recursive routine to remove nodes and nested children.

export const removeNodeWithChildren = (
  initalTree: TreeProps,
  nodeIndex: TreeItemIndex
): TreeProps => {
  let newTree: TreeProps = {
    rootItem: initalTree.rootItem,
    treeId: initalTree.treeId,
  };

  let node = initalTree[nodeIndex];
  // remove node from tree
  Object.entries(initalTree).map(([k, n]) => {
    if (k != node.index) newTree[k] = n;
  });

  // remove all reference of nodes from tree
  newTree = removeChildrenFromAllNodes(newTree, [node]);
  
  node.children?.map((c) => (newTree = removeNodeWithChildren(newTree, c)));
  return newTree;
};

const removeChildrenFromAllNodes = (
  initialTree: TreeProps,
  nodes: TreeItem[]
) => {
  let oldParentNodes = {};
  nodes.map((node) => {
    let oldParent = Object.values(initialTree).filter((n) =>
      n.children?.includes(node.index)
    )[0];
    if (!oldParent) return;
    let newParent =
      oldParentNodes[oldParent.index] ?? initialTree[oldParent.index];
    oldParentNodes[oldParent.index] = {
      ...(newParent ?? {}),
      children: newParent.children.filter((c: string) => c != node.index),
    };
    oldParentNodes[oldParent.index].hasChildren =
      oldParentNodes[oldParent.index].children?.length > 0;
  });
  return { ...initialTree, ...oldParentNodes };
};

The routine above seems to work by looking at the console.log(), but when i set the state with the new reduced tree i get the following error:

useTreeItemRenderContext.js:47 Uncaught TypeError: Cannot read properties of undefined (reading 'index')
    at useTreeItemRenderContext.js:47
    at Array.find (<anonymous>)
    at _a (useTreeItemRenderContext.js:47)
    at updateMemo (react-dom.development.js:15463)
    at Object.useMemo (react-dom.development.js:15920)
    at useMemo (react.development.js:1521)
    at useTreeItemRenderContext (useTreeItemRenderContext.js:39)
    at TreeItem (TreeItem.js:18)
    at renderWithHooks (react-dom.development.js:14803)
    at updateFunctionComponent (react-dom.development.js:17034)
(anonymous) @ useTreeItemRenderContext.js:47
_a @ useTreeItemRenderContext.js:47
updateMemo @ react-dom.development.js:15463
useMemo @ react-dom.development.js:15920
useMemo @ react.development.js:1521
useTreeItemRenderContext @ useTreeItemRenderContext.js:39
TreeItem @ TreeItem.js:18
renderWithHooks @ react-dom.development.js:14803
updateFunctionComponent @ react-dom.development.js:17034
beginWork @ react-dom.development.js:18610
callCallback @ react-dom.development.js:188
invokeGuardedCallbackDev @ react-dom.development.js:237
invokeGuardedCallback @ react-dom.development.js:292
beginWork$1 @ react-dom.development.js:23203
performUnitOfWork @ react-dom.development.js:22154
workLoopSync @ react-dom.development.js:22130
performSyncWorkOnRoot @ react-dom.development.js:21756
(anonymous) @ react-dom.development.js:11089
unstable_runWithPriority @ scheduler.development.js:653
runWithPriority$1 @ react-dom.development.js:11039
flushSyncCallbackQueueImpl @ react-dom.development.js:11084
flushSyncCallbackQueue @ react-dom.development.js:11072
discreteUpdates$1 @ react-dom.development.js:21893
discreteUpdates @ react-dom.development.js:806
dispatchDiscreteEvent @ react-dom.development.js:4168
2react_devtools_backend.js:2528 The above error occurred in the <TreeItem> component:
    in TreeItem (created by TreeItemChildren)
    in ul (created by TreeItemChildren)
    in TreeItemChildren (created by TreeManager)
    in div (created by TreeManager)
    in div (created by TreeManager)
    in TreeManager (created by ForwardRef)
    in ForwardRef (created by ForwardRef)
    in ForwardRef (created by Sidebar)
    in ForwardRef (created by ForwardRef)
   ....

react-complex-tree and MS Fluent UI Panel

Describe the bug
The drop event of react-complex-tree do not work in use with MS Fluent UI Panel

To Reproduce
I have created a codeSandBox, if use react-complex-tree directly un page, it work correctly, if use in ms Fluent ui Panel (click open button), the drop functionality do not work (the drag work, but the drop action do not work)

On drop, blue line freeze and onDrop event return nothing

https://codesandbox.io/s/fluentui-panel-and-react-complex-tree-p3e6qq

Desktop :

  • OS: Windows 11
  • Browser : chrome, firefox, edge
  • Version : latest

Some console.log() are left in the published code

Describe the bug
Some console.log() are left in the published code.

const getItemTitle = (index: TreeItemIndex) => {
console.log(index, environment.items, dnd.draggingPosition);
return environment.getItemTitle(environment.items[index]);
};

export const PropTable = ({ name }) => {
const props = useDynamicImport(name);
console.log(props, name);
if (!props) {

Screenshots
image

Ability to move `DragBetweenLine` intersection and rendering to beneath an expanded item's `<ul>`

Is your feature request related to a problem? Please describe.
The default UX for reordering dragged items beneath an expanded item does not feel intuitive and takes some getting used to:

Screen.Recording.2022-09-09.at.4.00.11.AM.mov

especially as the reorder behavior above the top child is not consistent for children of the expanded item:

Screen.Recording.2022-09-09.at.4.09.23.AM.mov

Describe the solution you'd like
I would like to be able to hover my dragged items at the bottom of the expanded layer's <ul> element and get drag between line feedback there (rather than directly beneath the expanded parent) for dropping the items beneath the expanded layer.

Describe alternatives you've considered
Perhaps this could be a non-breaking change by providing a prop to the tree environment similar to defaultInteractionMode

Accessibility in Storybook

Describe the bug
A clear and concise description of what the bug is.

When trying to test the accessibility of the library, you can't focus on the tree and the library does not process key events that it is supposed to, like the down arrow moving lower in the tree at that level.

To Reproduce

Try the keyboard shortcuts described in https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html and you'll find that sometimes the focus shows up and sometimes it doesn't. The most troubling issue is when certain arrow keys accidentally take you out of the tree.

Expected behavior

The keyboard behavior described in https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html should work.

Screenshots
n.a.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser: Firefox w/ NVDA
  • Version: 98.0.2

Smartphone (please complete the following information):

n.a.

Additional context
Add any other context about the problem here.

Event for drag-starting

Hey,

I could not find an "onStartDrag"-event when the user started dragging an item.
Background: I want to highlight possible drop-targets between two trees when the user starts dragging.

Any clue on this, or is this feature simply missing?

Thanks in advance.

Cheers,
Dom

Control search term

Is your feature request related to a problem? Please describe.
I would like to present a search custom search input outside the tree managed by myself. Additionally i would like to programatically set the search term via props in some other situations (restoring state after application is resumed).

Describe the solution you'd like
Introduce the ability to control the search term, either in controlled tree environment or on the ref. I would have thought that controlled tree environment would be the appropriate place but I haven't used the library for long

Describe alternatives you've considered
I could hack the render search input props to render outside of the tree somehow but this feels wrong. I could also implement search myself but having a custom item and managing the state for search and highlighting myself

Additional context
None, great library BTW, I have enjoyed using it so far

useMemoizedObject causes errors

Describe the bug

In the console, I am seeing:

Warning: The final argument passed to useMemo changed size between renders. The order and size of this array must remain constant.

The following code clearly violates this requirement:

return useMemo(() => original, Object.values(original));

To Reproduce
Steps to reproduce the behavior:

I don't have a minimial reproducable example yet... but the source of the bug is obvious.

Expected behavior
The library should not cause errors.

Additional context

Full error:
Warning: The final argument passed to useMemo changed size between renders. The order and size of this array must remain constant.

Previous: [props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE),
    children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("ul", {
      className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_ROOT, _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LIST),
      ...props.containerProps,
      children: props.children
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 18,
      columnNumber: 13
    }, undefined)
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 17,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("ul", {
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LIST),
    ...props.containerProps,
    children: props.children
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 28,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("li", {
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE, (props.context.isSelected || props.context.isDraggingOver) && _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_SELECTED),
    ...props.context.itemContainerWithChildrenProps,
    children: [/*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
      className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CONTENT, `${_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CONTENT}-${props.depth}`),
      ...props.context.itemContainerWithoutChildrenProps,
      ...props.context.interactiveElementProps,
      children: [props.item.hasChildren ? props.arrow : /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
        className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET_NONE
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 53,
        columnNumber: 21
      }, undefined), props.item.data.icon !== undefined ? props.item.data.icon === null ? null : /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Icon"], {
        icon: props.item.data.icon,
        className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_ICON
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 57,
        columnNumber: 25
      }, undefined) : (() => {
        const icon = !props.item.hasChildren ? 'document' : props.context.isExpanded ? 'folder-open' : 'folder-close';
        return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Icon"], {
          icon: icon,
          className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_ICON
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 69,
          columnNumber: 32
        }, undefined);
      })(), props.title]
    }, void 0, true, {
      fileName: _jsxFileName,
      lineNumber: 42,
      columnNumber: 13
    }, undefined), /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
      className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].COLLAPSE),
      style: props.context.isExpanded ? {
        height: 'auto',
        overflowY: 'visible',
        transition: 'none 0s ease 0s'
      } : {},
      children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Collapse"], {
        isOpen: props.context.isExpanded,
        transitionDuration: 0,
        children: props.children
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 86,
        columnNumber: 17
      }, undefined)
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 74,
      columnNumber: 13
    }, undefined)]
  }, void 0, true, {
    fileName: _jsxFileName,
    lineNumber: 34,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Icon"], {
    icon: "chevron-right",
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET, props.context.isExpanded ? _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET_OPEN : _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET_CLOSED),
    ...props.context.arrowProps
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 94,
    columnNumber: 9
  }, undefined), _ref => {
    let {
      title,
      context,
      info
    } = _ref;

    if (!info.isSearching || !context.isSearchMatching || !info.search) {
      return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
        className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LABEL,
        children: title
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 108,
        columnNumber: 20
      }, undefined);
    } else {
      const startIndex = title.toLowerCase().indexOf(info.search.toLowerCase());
      return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(react__WEBPACK_IMPORTED_MODULE_1___default.a.Fragment, {
        children: [startIndex > 0 && /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
          children: title.slice(0, startIndex)
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 113,
          columnNumber: 40
        }, undefined), /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
          className: "rct-tree-item-search-highlight",
          children: title.slice(startIndex, startIndex + info.search.length)
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 114,
          columnNumber: 21
        }, undefined), startIndex + info.search.length < title.length && /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
          children: title.slice(startIndex + info.search.length, title.length)
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 118,
          columnNumber: 25
        }, undefined)]
      }, void 0, true, {
        fileName: _jsxFileName,
        lineNumber: 112,
        columnNumber: 17
      }, undefined);
    }
  }, _ref2 => {
    let {
      draggingPosition,
      lineProps
    } = _ref2;
    return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", { ...lineProps,
      style: {
        position: 'absolute',
        right: '0',
        top: draggingPosition.targetType === 'between-items' && draggingPosition.linePosition === 'top' ? '0px' : draggingPosition.targetType === 'between-items' && draggingPosition.linePosition === 'bottom' ? '-4px' : '-2px',
        left: `${draggingPosition.depth * 23}px`,
        height: '4px',
        backgroundColor: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Colors"].BLUE3
      }
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 128,
      columnNumber: 9
    }, undefined);
  }, props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("form", { ...props.formProps,
    style: {
      display: 'contents'
    },
    children: [/*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
      className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LABEL,
      children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("input", { ...props.inputProps,
        ref: props.inputRef,
        className: "rct-tree-item-renaming-input"
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 151,
        columnNumber: 17
      }, undefined)
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 150,
      columnNumber: 13
    }, undefined), /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
      className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_SECONDARY_LABEL,
      children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Button"], {
        icon: "tick",
        ...props.submitButtonProps,
        type: "submit",
        minimal: true,
        small: true
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 158,
        columnNumber: 17
      }, undefined)
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 157,
      columnNumber: 13
    }, undefined)]
  }, void 0, true, {
    fileName: _jsxFileName,
    lineNumber: 149,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
    className: cx('rct-tree-search-input-container'),
    children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["InputGroup"], { ...props.inputProps,
      placeholder: "Search..."
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 171,
      columnNumber: 13
    }, undefined)
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 170,
    columnNumber: 9
  }, undefined), 1, [object Object], item => item.data, [object Object], [object Object], [object Object], function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        var _a;

        return __assign(__assign({}, old), {
          expandedItems: __spreadArray(__spreadArray([], (_a = old.expandedItems) !== null && _a !== void 0 ? _a : []), [item.index])
        });
      });
      (_a = props.onExpandItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId); //const itemsToLoad = item.children?.filter(itemId => currentItems[itemId] === undefined) ?? [];
      //dataProvider.getTreeItems(itemsToLoad).then(items => {
      //  writeItems(items.map(item => ({ [item.index]: item })).reduce((a, b) => ({...a, ...b}), {}));
      //  setViewState(viewState => ({ ...viewState, expandedItems: [...viewState.expandedItems ?? [], item.index] }));
      //});
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        var _a;

        return __assign(__assign({}, old), {
          expandedItems: (_a = old.expandedItems) === null || _a === void 0 ? void 0 : _a.filter(function (id) {
            return id !== item.index;
          })
        });
      });
      (_a = props.onCollapseItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (items, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          selectedItems: items
        });
      });
      (_a = props.onSelectItems) === null || _a === void 0 ? void 0 : _a.call(props, items, treeId);
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          focusedItem: item.index
        });
      });
      (_a = props.onFocusItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          renamingItem: item.index
        });
      });
      (_a = props.onStartRenamingItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          renamingItem: undefined
        });
      });
      (_a = props.onAbortRenamingItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (item, name, treeId) {
      return __awaiter(void 0, void 0, void 0, function () {
        var newItem;

        var _a;

        var _b;

        return __generator(this, function (_c) {
          switch (_c.label) {
            case 0:
              return [4
              /*yield*/
              , dataProvider.onRenameItem(item, name)];

            case 1:
              _c.sent();

              amendViewState(treeId, function (old) {
                return __assign(__assign({}, old), {
                  renamingItem: undefined
                });
              });
              return [4
              /*yield*/
              , dataProvider.getTreeItem(item.index)];

            case 2:
              newItem = _c.sent();
              writeItems((_a = {}, _a[item.index] = newItem, _a));
              (_b = props.onRenameItem) === null || _b === void 0 ? void 0 : _b.call(props, item, name, treeId);
              return [2
              /*return*/
              ];
          }
        });
      });
    }, function (items, target) {
      return __awaiter(void 0, void 0, void 0, function () {
        var _loop_1, _i, items_1, item;

        var _a, _b, _c, _d, _e;

        return __generator(this, function (_f) {
          switch (_f.label) {
            case 0:
              _loop_1 = function (item) {
                var parent_1, newParent, newParentChildren, isOldItemPriorToNewItem;
                return __generator(this, function (_g) {
                  switch (_g.label) {
                    case 0:
                      parent_1 = Object.values(currentItems).find(function (potentialParent) {
                        var _a;

                        return (_a = potentialParent.children) === null || _a === void 0 ? void 0 : _a.includes(item.index);
                      });
                      newParent = currentItems[target.parentItem];

                      if (!parent_1) {
                        throw Error("Could not find parent of item \"" + item.index + "\"");
                      }

                      if (!parent_1.children) {
                        throw Error("Parent \"" + parent_1.index + "\" of item \"" + item.index + "\" did not have any children");
                      }

                      if (!(target.targetType === 'item')) return [3
                      /*break*/
                      , 5];
                      if (!(target.targetItem === parent_1.index)) return [3
                      /*break*/
                      , 1];
                      return [3
                      /*break*/
                      , 4];

                    case 1:
                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(parent_1.index, parent_1.children.filter(function (child) {
                        return child !== item.index;
                      }))];

                    case 2:
                      _g.sent();

                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(target.targetItem, __spreadArray(__spreadArray([], (_a = currentItems[target.targetItem].children) !== null && _a !== void 0 ? _a : []), [item.index]))];

                    case 3:
                      _g.sent();

                      _g.label = 4;

                    case 4:
                      return [3
                      /*break*/
                      , 10];

                    case 5:
                      newParentChildren = __spreadArray([], (_b = newParent.children) !== null && _b !== void 0 ? _b : []).filter(function (child) {
                        return child !== item.index;
                      });
                      if (!(target.parentItem === parent_1.index)) return [3
                      /*break*/
                      , 7];
                      isOldItemPriorToNewItem = ((_d = ((_c = newParent.children) !== null && _c !== void 0 ? _c : []).findIndex(function (child) {
                        return child === item.index;
                      })) !== null && _d !== void 0 ? _d : Infinity) < target.childIndex;
                      newParentChildren.splice(target.childIndex - (isOldItemPriorToNewItem ? 1 : 0), 0, item.index);
                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(target.parentItem, newParentChildren)];

                    case 6:
                      _g.sent();

                      return [3
                      /*break*/
                      , 10];

                    case 7:
                      newParentChildren.splice(target.childIndex, 0, item.index);
                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(parent_1.index, parent_1.children.filter(function (child) {
                        return child !== item.index;
                      }))];

                    case 8:
                      _g.sent();

                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(target.parentItem, newParentChildren)];

                    case 9:
                      _g.sent();

                      _g.label = 10;

                    case 10:
                      return [2
                      /*return*/
                      ];
                  }
                });
              };

              _i = 0, items_1 = items;
              _f.label = 1;

            case 1:
              if (!(_i < items_1.length)) return [3
              /*break*/
              , 4];
              item = items_1[_i];
              return [5
              /*yield**/
              , _loop_1(item)];

            case 2:
              _f.sent();

              _f.label = 3;

            case 3:
              _i++;
              return [3
              /*break*/
              , 1];

            case 4:
              (_e = props.onDrop) === null || _e === void 0 ? void 0 : _e.call(props, items, target);
              return [2
              /*return*/
              ];
          }
        });
      });
    }, function (itemIds) {
      var _a;

      var _b; // Batch individual fetch-item-calls together


      if (missingItemIds.current.length === 0) {
        setTimeout(function () {
          dataProvider.getTreeItems(missingItemIds.current).then(function (items) {
            writeItems(items.map(function (item) {
              var _a;

              return _a = {}, _a[item.index] = item, _a;
            }).reduce(function (a, b) {
              return __assign(__assign({}, a), b);
            }, {}));
          });
          missingItemIds.current = [];
        });
      }

      (_a = missingItemIds.current).push.apply(_a, itemIds);

      (_b = props.onMissingItems) === null || _b === void 0 ? void 0 : _b.call(props, itemIds);
    }]
Incoming: [props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE),
    children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("ul", {
      className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_ROOT, _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LIST),
      ...props.containerProps,
      children: props.children
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 18,
      columnNumber: 13
    }, undefined)
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 17,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("ul", {
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LIST),
    ...props.containerProps,
    children: props.children
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 28,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("li", {
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE, (props.context.isSelected || props.context.isDraggingOver) && _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_SELECTED),
    ...props.context.itemContainerWithChildrenProps,
    children: [/*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
      className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CONTENT, `${_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CONTENT}-${props.depth}`),
      ...props.context.itemContainerWithoutChildrenProps,
      ...props.context.interactiveElementProps,
      children: [props.item.hasChildren ? props.arrow : /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
        className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET_NONE
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 53,
        columnNumber: 21
      }, undefined), props.item.data.icon !== undefined ? props.item.data.icon === null ? null : /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Icon"], {
        icon: props.item.data.icon,
        className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_ICON
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 57,
        columnNumber: 25
      }, undefined) : (() => {
        const icon = !props.item.hasChildren ? 'document' : props.context.isExpanded ? 'folder-open' : 'folder-close';
        return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Icon"], {
          icon: icon,
          className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_ICON
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 69,
          columnNumber: 32
        }, undefined);
      })(), props.title]
    }, void 0, true, {
      fileName: _jsxFileName,
      lineNumber: 42,
      columnNumber: 13
    }, undefined), /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
      className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].COLLAPSE),
      style: props.context.isExpanded ? {
        height: 'auto',
        overflowY: 'visible',
        transition: 'none 0s ease 0s'
      } : {},
      children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Collapse"], {
        isOpen: props.context.isExpanded,
        transitionDuration: 0,
        children: props.children
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 86,
        columnNumber: 17
      }, undefined)
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 74,
      columnNumber: 13
    }, undefined)]
  }, void 0, true, {
    fileName: _jsxFileName,
    lineNumber: 34,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Icon"], {
    icon: "chevron-right",
    className: cx(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET, props.context.isExpanded ? _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET_OPEN : _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_CARET_CLOSED),
    ...props.context.arrowProps
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 94,
    columnNumber: 9
  }, undefined), _ref => {
    let {
      title,
      context,
      info
    } = _ref;

    if (!info.isSearching || !context.isSearchMatching || !info.search) {
      return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
        className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LABEL,
        children: title
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 108,
        columnNumber: 20
      }, undefined);
    } else {
      const startIndex = title.toLowerCase().indexOf(info.search.toLowerCase());
      return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(react__WEBPACK_IMPORTED_MODULE_1___default.a.Fragment, {
        children: [startIndex > 0 && /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
          children: title.slice(0, startIndex)
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 113,
          columnNumber: 40
        }, undefined), /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
          className: "rct-tree-item-search-highlight",
          children: title.slice(startIndex, startIndex + info.search.length)
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 114,
          columnNumber: 21
        }, undefined), startIndex + info.search.length < title.length && /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
          children: title.slice(startIndex + info.search.length, title.length)
        }, void 0, false, {
          fileName: _jsxFileName,
          lineNumber: 118,
          columnNumber: 25
        }, undefined)]
      }, void 0, true, {
        fileName: _jsxFileName,
        lineNumber: 112,
        columnNumber: 17
      }, undefined);
    }
  }, _ref2 => {
    let {
      draggingPosition,
      lineProps
    } = _ref2;
    return /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", { ...lineProps,
      style: {
        position: 'absolute',
        right: '0',
        top: draggingPosition.targetType === 'between-items' && draggingPosition.linePosition === 'top' ? '0px' : draggingPosition.targetType === 'between-items' && draggingPosition.linePosition === 'bottom' ? '-4px' : '-2px',
        left: `${draggingPosition.depth * 23}px`,
        height: '4px',
        backgroundColor: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Colors"].BLUE3
      }
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 128,
      columnNumber: 9
    }, undefined);
  }, props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("form", { ...props.formProps,
    style: {
      display: 'contents'
    },
    children: [/*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
      className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_LABEL,
      children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("input", { ...props.inputProps,
        ref: props.inputRef,
        className: "rct-tree-item-renaming-input"
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 151,
        columnNumber: 17
      }, undefined)
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 150,
      columnNumber: 13
    }, undefined), /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("span", {
      className: _blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Classes"].TREE_NODE_SECONDARY_LABEL,
      children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["Button"], {
        icon: "tick",
        ...props.submitButtonProps,
        type: "submit",
        minimal: true,
        small: true
      }, void 0, false, {
        fileName: _jsxFileName,
        lineNumber: 158,
        columnNumber: 17
      }, undefined)
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 157,
      columnNumber: 13
    }, undefined)]
  }, void 0, true, {
    fileName: _jsxFileName,
    lineNumber: 149,
    columnNumber: 9
  }, undefined), props => /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])("div", {
    className: cx('rct-tree-search-input-container'),
    children: /*#__PURE__*/Object(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_2__["jsxDEV"])(_blueprintjs_core__WEBPACK_IMPORTED_MODULE_0__["InputGroup"], { ...props.inputProps,
      placeholder: "Search..."
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 171,
      columnNumber: 13
    }, undefined)
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 170,
    columnNumber: 9
  }, undefined), 1, [object Object], item => item.data, false, [object Object], [object Object], [object Object], function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        var _a;

        return __assign(__assign({}, old), {
          expandedItems: __spreadArray(__spreadArray([], (_a = old.expandedItems) !== null && _a !== void 0 ? _a : []), [item.index])
        });
      });
      (_a = props.onExpandItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId); //const itemsToLoad = item.children?.filter(itemId => currentItems[itemId] === undefined) ?? [];
      //dataProvider.getTreeItems(itemsToLoad).then(items => {
      //  writeItems(items.map(item => ({ [item.index]: item })).reduce((a, b) => ({...a, ...b}), {}));
      //  setViewState(viewState => ({ ...viewState, expandedItems: [...viewState.expandedItems ?? [], item.index] }));
      //});
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        var _a;

        return __assign(__assign({}, old), {
          expandedItems: (_a = old.expandedItems) === null || _a === void 0 ? void 0 : _a.filter(function (id) {
            return id !== item.index;
          })
        });
      });
      (_a = props.onCollapseItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (items, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          selectedItems: items
        });
      });
      (_a = props.onSelectItems) === null || _a === void 0 ? void 0 : _a.call(props, items, treeId);
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          focusedItem: item.index
        });
      });
      (_a = props.onFocusItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          renamingItem: item.index
        });
      });
      (_a = props.onStartRenamingItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (item, treeId) {
      var _a;

      amendViewState(treeId, function (old) {
        return __assign(__assign({}, old), {
          renamingItem: undefined
        });
      });
      (_a = props.onAbortRenamingItem) === null || _a === void 0 ? void 0 : _a.call(props, item, treeId);
    }, function (item, name, treeId) {
      return __awaiter(void 0, void 0, void 0, function () {
        var newItem;

        var _a;

        var _b;

        return __generator(this, function (_c) {
          switch (_c.label) {
            case 0:
              return [4
              /*yield*/
              , dataProvider.onRenameItem(item, name)];

            case 1:
              _c.sent();

              amendViewState(treeId, function (old) {
                return __assign(__assign({}, old), {
                  renamingItem: undefined
                });
              });
              return [4
              /*yield*/
              , dataProvider.getTreeItem(item.index)];

            case 2:
              newItem = _c.sent();
              writeItems((_a = {}, _a[item.index] = newItem, _a));
              (_b = props.onRenameItem) === null || _b === void 0 ? void 0 : _b.call(props, item, name, treeId);
              return [2
              /*return*/
              ];
          }
        });
      });
    }, function (items, target) {
      return __awaiter(void 0, void 0, void 0, function () {
        var _loop_1, _i, items_1, item;

        var _a, _b, _c, _d, _e;

        return __generator(this, function (_f) {
          switch (_f.label) {
            case 0:
              _loop_1 = function (item) {
                var parent_1, newParent, newParentChildren, isOldItemPriorToNewItem;
                return __generator(this, function (_g) {
                  switch (_g.label) {
                    case 0:
                      parent_1 = Object.values(currentItems).find(function (potentialParent) {
                        var _a;

                        return (_a = potentialParent.children) === null || _a === void 0 ? void 0 : _a.includes(item.index);
                      });
                      newParent = currentItems[target.parentItem];

                      if (!parent_1) {
                        throw Error("Could not find parent of item \"" + item.index + "\"");
                      }

                      if (!parent_1.children) {
                        throw Error("Parent \"" + parent_1.index + "\" of item \"" + item.index + "\" did not have any children");
                      }

                      if (!(target.targetType === 'item')) return [3
                      /*break*/
                      , 5];
                      if (!(target.targetItem === parent_1.index)) return [3
                      /*break*/
                      , 1];
                      return [3
                      /*break*/
                      , 4];

                    case 1:
                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(parent_1.index, parent_1.children.filter(function (child) {
                        return child !== item.index;
                      }))];

                    case 2:
                      _g.sent();

                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(target.targetItem, __spreadArray(__spreadArray([], (_a = currentItems[target.targetItem].children) !== null && _a !== void 0 ? _a : []), [item.index]))];

                    case 3:
                      _g.sent();

                      _g.label = 4;

                    case 4:
                      return [3
                      /*break*/
                      , 10];

                    case 5:
                      newParentChildren = __spreadArray([], (_b = newParent.children) !== null && _b !== void 0 ? _b : []).filter(function (child) {
                        return child !== item.index;
                      });
                      if (!(target.parentItem === parent_1.index)) return [3
                      /*break*/
                      , 7];
                      isOldItemPriorToNewItem = ((_d = ((_c = newParent.children) !== null && _c !== void 0 ? _c : []).findIndex(function (child) {
                        return child === item.index;
                      })) !== null && _d !== void 0 ? _d : Infinity) < target.childIndex;
                      newParentChildren.splice(target.childIndex - (isOldItemPriorToNewItem ? 1 : 0), 0, item.index);
                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(target.parentItem, newParentChildren)];

                    case 6:
                      _g.sent();

                      return [3
                      /*break*/
                      , 10];

                    case 7:
                      newParentChildren.splice(target.childIndex, 0, item.index);
                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(parent_1.index, parent_1.children.filter(function (child) {
                        return child !== item.index;
                      }))];

                    case 8:
                      _g.sent();

                      return [4
                      /*yield*/
                      , dataProvider.onChangeItemChildren(target.parentItem, newParentChildren)];

                    case 9:
                      _g.sent();

                      _g.label = 10;

                    case 10:
                      return [2
                      /*return*/
                      ];
                  }
                });
              };

              _i = 0, items_1 = items;
              _f.label = 1;

            case 1:
              if (!(_i < items_1.length)) return [3
              /*break*/
              , 4];
              item = items_1[_i];
              return [5
              /*yield**/
              , _loop_1(item)];

            case 2:
              _f.sent();

              _f.label = 3;

            case 3:
              _i++;
              return [3
              /*break*/
              , 1];

            case 4:
              (_e = props.onDrop) === null || _e === void 0 ? void 0 : _e.call(props, items, target);
              return [2
              /*return*/
              ];
          }
        });
      });
    }, function (itemIds) {
      var _a;

      var _b; // Batch individual fetch-item-calls together


      if (missingItemIds.current.length === 0) {
        setTimeout(function () {
          dataProvider.getTreeItems(missingItemIds.current).then(function (items) {
            writeItems(items.map(function (item) {
              var _a;

              return _a = {}, _a[item.index] = item, _a;
            }).reduce(function (a, b) {
              return __assign(__assign({}, a), b);
            }, {}));
          });
          missingItemIds.current = [];
        });
      }

      (_a = missingItemIds.current).push.apply(_a, itemIds);

      (_b = props.onMissingItems) === null || _b === void 0 ? void 0 : _b.call(props, itemIds);
    }]
    in ForwardRef (created by ForwardRef)
    in ForwardRef (at Explorer.tsx:192)
    in FileTree (at Explorer.tsx:209)
    in div (at Explorer.tsx:206)
    in Explorer (at App.tsx:161)
    in div (at App.tsx:157)
    in div (at App.tsx:155)
    in App (at src/index.tsx:57)
    in Provider (at src/index.tsx:54)
    in StrictMode (at src/index.tsx:53)

ClickArrowToExpand doesn't select the TreeItem.

Describe the bug
When using ClickArrowToExpand, a single click on the whole TreeItem doesn't select it.

To Reproduce
Steps to reproduce the behavior:

  • defaultInteractionMode={InteractionMode.ClickArrowToExpand}
  • click on a node (but not on its arrow)
  • observe that it is focused but not selected

Expected behavior

  • A click on the arrow should expand (it does).
  • A click on the rest of the item should select it (it doesn't).

Screenshots
image

Here the node "world" is selected because it has been expanded. I then clicked on the "The living room" node, and it was focused but not selected.

requestAnimationFrame is not canceled when component is unmounted

None of the uses of requestAnimationFrame in the code are canceled when a component is unmounted. This can lead to errors like this:

    console.error
      Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
          in TreeManager (created by ForwardRef)
          in ForwardRef (created by ForwardRef)
          in ForwardRef (at Explorer.tsx:395)
          in div (at Explorer.tsx:394)
          in ForwardRef (created by ForwardRef)
          in DragAndDropProvider (created by ForwardRef)
          in InteractionManagerProvider (created by ForwardRef)
          in ForwardRef (at Explorer.tsx:383)
          in FileTree (at Explorer.tsx:417)
          in div (at Explorer.tsx:414)
          in Explorer (at Explorer.test.tsx:77)
          in HotkeysProvider (at test/index.tsx:142)
          in Provider (at test/index.tsx:140)

The suggested fix of using useEffect is not easy since all of the uses occur in other callbacks where hooks cannot be used.

Change data source

Whether the data source can be changed during operation, such as changing the data source when clicking the button

Non-contiguous multi-selection

โš ๏ธ EDIT: I realize now that this is already implemented so in the comment I added, I address the unexpected behavior I'm experiencing on Mac which may be a bug (please skip reading the below and just read the comment)

Is your feature request related to a problem? Please describe.

As far as I know, the only way to perform a multi-selection is via holding down shift to select a contiguous set of nodes between two points in the tree. I currently cannot pick a set of non-contiguous nodes at arbitrary positions in the tree to form a multi-selection.

Describe the solution you'd like

I would love to be able to hold down cmd/ctrl while selecting nodes to arbitrarily build a multi-selection. Then when I drag that selection of nodes to a new position, it would be great to have them all become siblings after placing in the drop target, sorted in their previous vertical order.

Additional context
Here's an example of that type of behavior in Photoshop's layers panel - clicking on layers to select while holding down cmd

Screen.Recording.2022-06-02.at.7.05.43.PM.mov

๐Ÿ™Œ Absolutely stellar work on this project by the way; it's the most compelling and polished React tree component I've come across! ๐Ÿ˜

Ability to set the drag feedback image

Is your feature request related to a problem? Please describe.

When I attempt to set a custom drag feedback image via something like...

// Item.tsx

const generateDragGhostStyle = (event: React.DragEvent) => {
  event.dataTransfer.setDragImage(new Image(), 0, 0);
};

...

<button
  {...context.interactiveElementProps}
  onDragStart={generateDragGhostStyle}
  >
  ...
</button>

It completely messes up the drop position detection and scrunches it to a region at the very top of the tree. It seems an offset is calculated internally that my above code snippet overrides.

Describe the solution you'd like

I'd love to be able to provide a renderDragImage prop to the tree environment which corresponds to setDragImage's imgElement argument.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.