Git Product home page Git Product logo

react-arborist's Introduction

Logo

React Arborist

See the Demos

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.

Here is a Gmail sidebar clone built with react-arborist.

Gmail sidebar clone built with react-arborist

Features

  • Drag and drop sorting
  • Open/close folders
  • Inline renaming
  • Virtualized rendering
  • Custom styling
  • Keyboard navigation
  • Aria attributes
  • Tree filtering
  • Selection synchronization
  • Callbacks (onScroll, onActivate, onSelect)
  • Controlled or uncontrolled trees

Installation

yarn add react-arborist
npm install react-arborist

Examples

Assume our data is this:

const data = [
  { id: "1", name: "Unread" },
  { id: "2", name: "Threads" },
  {
    id: "3",
    name: "Chat Rooms",
    children: [
      { id: "c1", name: "General" },
      { id: "c2", name: "Random" },
      { id: "c3", name: "Open Source Projects" },
    ],
  },
  {
    id: "4",
    name: "Direct Messages",
    children: [
      { id: "d1", name: "Alice" },
      { id: "d2", name: "Bob" },
      { id: "d3", name: "Charlie" },
    ],
  },
];

The Simplest Tree

Use all the defaults. The initialData prop makes the tree an uncontrolled component. Create, move, rename, and delete will be handled internally.

function App() {
  return <Tree initialData={data} />;
}

image

Demo

Customize the Appearance

We provide our own dimensions and our own Node component.

function App() {
  return (
    <Tree
      initialData={data}
      openByDefault={false}
      width={600}
      height={1000}
      indent={24}
      rowHeight={36}
      overscanCount={1}
      paddingTop={30}
      paddingBottom={10}
      padding={25 /* sets both */}
    >
      {Node}
    </Tree>
  );
}

function Node({ node, style, dragHandle }) {
  /* This node instance can do many things. See the API reference. */
  return (
    <div style={style} ref={dragHandle}>
      {node.isLeaf ? "๐Ÿ" : "๐Ÿ—€"}
      {node.data.name}
    </div>
  );
}

image

Demo

Control the Tree data

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.

function App() {
  /* Handle the data modifications outside the tree component */
  const onCreate = ({ parentId, index, type }) => {};
  const onRename = ({ id, name }) => {};
  const onMove = ({ dragIds, parentId, index }) => {};
  const onDelete = ({ ids }) => {};

  return (
    <Tree
      data={data}
      onCreate={onCreate}
      onRename={onRename}
      onMove={onMove}
      onDelete={onDelete}
    />
  );
}

Tree Filtering

Providing a non-empty searchTerm will only show nodes that match. If a child matches, all its parents also match. Internal nodes are opened when filtering. You can provide your own searchMatch function, or use the default.

function App() {
  const term = useSearchTermString()
  <Tree
    data={data}
    searchTerm={term}
    searchMatch={
      (node, term) => node.data.name.toLowerCase().includes(term.toLowerCase())
    }
  />
}

Sync the Selection

It's common to open something elsewhere in the app, but have the tree reflect the new selection.

Passing an id to the selection prop will select and scroll to that node whenever that id changes.

function App() {
  const chatId = useCurrentChatId();

  /* 
    Whenever the currentChatRoomId changes, 
    the tree will automatically select it and scroll to it. 
  */

  return <Tree initialData={data} selection={chatId} />;
}

Use the Tree Api Instance

You can access the Tree Api in the parent component by giving a ref to the tree.

function App() {
  const treeRef = useRef();

  useEffect(() => {
    const tree = treeRef.current;
    tree.selectAll();
    /* See the Tree API reference for all you can do with it. */
  }, []);

  return <Tree initialData={data} ref={treeRef} />;
}

Data with Different Property Names

The idAccessor and childrenAccessor props allow you to specify the children and id fields in your data.

function App() {
  const data = [
    {
      category: "Food",
      subCategories: [{ category: "Restaurants" }, { category: "Groceries" }],
    },
  ];
  return (
    <Tree
      data={data}
      /* An accessor can provide a string property name */
      idAccessor="category"
      /* or a function with the data as the argument */
      childrenAccessor={(d) => d.subCategories}
    />
  );
}

Custom Rendering

Render every single piece of the tree yourself. See the API reference for the props passed to each renderer.

function App() {
  return (
    <Tree
      data={data}
      /* The outer most element in the list */
      renderRow={MyRow}
      /* The "ghost" element that follows the mouse as you drag */
      renderDragPreview={MyDragPreview}
      /* The line that shows where an element will be dropped */
      renderCursor={MyCursor}
    >
      {/* The inner element that shows the indentation and data */}
      {MyNode}
    </Tree>
  );
}

Dynamic sizing

You can add a ref to it with this package ZeeCoder/use-resize-observer

That hook will return the height and width of the parent whenever it changes. You then pass these numbers to the Tree.

const { ref, width, height } = useResizeObserver();
 
<div className="parent" ref={ref}>
  <Tree height={height} width={width} />
</div>

API Reference

Tree Component Props

These are all the props you can pass to the Tree component.

interface TreeProps<T> {
  /* Data Options */
  data?: readonly T[];
  initialData?: readonly T[];

  /* Data Handlers */
  onCreate?: handlers.CreateHandler<T>;
  onMove?: handlers.MoveHandler<T>;
  onRename?: handlers.RenameHandler<T>;
  onDelete?: handlers.DeleteHandler<T>;

  /* Renderers*/
  children?: ElementType<renderers.NodeRendererProps<T>>;
  renderRow?: ElementType<renderers.RowRendererProps<T>>;
  renderDragPreview?: ElementType<renderers.DragPreviewProps>;
  renderCursor?: ElementType<renderers.CursorProps>;
  renderContainer?: ElementType<{}>;

  /* Sizes */
  rowHeight?: number;
  overscanCount?: number;
  width?: number | string;
  height?: number;
  indent?: number;
  paddingTop?: number;
  paddingBottom?: number;
  padding?: number;

  /* Config */
  childrenAccessor?: string | ((d: T) => T[] | null);
  idAccessor?: string | ((d: T) => string);
  openByDefault?: boolean;
  selectionFollowsFocus?: boolean;
  disableMultiSelection?: boolean;
  disableEdit?: string | boolean | BoolFunc<T>;
  disableDrag?: string | boolean | BoolFunc<T>;
  disableDrop?:
    | string
    | boolean
    | ((args: {
        parentNode: NodeApi<T>;
        dragNodes: NodeApi<T>[];
        index: number;
      }) => boolean);

  /* Event Handlers */
  onActivate?: (node: NodeApi<T>) => void;
  onSelect?: (nodes: NodeApi<T>[]) => void;
  onScroll?: (props: ListOnScrollProps) => void;
  onToggle?: (id: string) => void;
  onFocus?: (node: NodeApi<T>) => void;

  /* Selection */
  selection?: string;

  /* Open State */
  initialOpenState?: OpenMap;

  /* Search */
  searchTerm?: string;
  searchMatch?: (node: NodeApi<T>, searchTerm: string) => boolean;

  /* Extra */
  className?: string | undefined;
  rowClassName?: string | undefined;

  dndRootElement?: globalThis.Node | null;
  onClick?: MouseEventHandler;
  onContextMenu?: MouseEventHandler;
  dndManager?: DragDropManager;
}

Row Component Props

The <RowRenderer> is responsible for attaching the drop ref, the row style (top, height) and the aria-attributes. The default should work fine for most use cases, but it can be replaced by your own component if you need. See the renderRow prop in the <Tree> component.

type RowRendererProps<T> = {
  node: NodeApi<T>;
  innerRef: (el: HTMLDivElement | null) => void;
  attrs: HTMLAttributes<any>;
  children: ReactElement;
};

Node Component Props

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.

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.

export type NodeRendererProps<T> = {
  style: CSSProperties;
  node: NodeApi<T>;
  tree: TreeApi<T>;
  dragHandle?: (el: HTMLDivElement | null) => void;
  preview?: boolean;
};

DragPreview Component Props

The <DragPreview> is responsible for showing a "ghost" version of the node being dragged. The default is a semi-transparent version of the NodeRenderer and should work fine for most people. To customize it, pass your new component to the renderDragPreview prop.

type DragPreviewProps = {
  offset: XYCoord | null;
  mouse: XYCoord | null;
  id: string | null;
  dragIds: string[];
  isDragging: boolean;
};

Cursor Component Props

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.

export type CursorProps = {
  top: number;
  left: number;
  indent: number;
};

Node API Reference

State Properties

All these properties on the node instance return booleans related to the state of the node.

node.isRoot

Returns true if this is the root node. The root node is added internally by react-arborist and not shown in the UI.

node.isLeaf

Returns true if the children property is not an array.

node.isInternal

Returns true if the children property is an array.

node.isOpen

Returns true if node is internal and in an open state.

node.isEditing

Returns true if this node is currently being edited. Use this property in the NodeRenderer to render the rename form.

node.isSelected

Returns true if node is selected.

node.isSelectedStart

Returns true if node is the first of a contiguous group of selected nodes. Useful for styling.

node.isSelectedEnd

Returns true if node is the last of a contiguous group of selected nodes. Useful for styling.

node.isOnlySelection

Returns true if node is the only node selected in the tree.

node.isFocused

Returns true if node is focused.

node.isDragging

Returns true if node is being dragged.

node.willReceiveDrop

Returns true if node is internal and the user is hovering a dragged node over it.

node.state

Returns an object with all the above properties as keys and boolean values. Useful for adding class names to an element with a library like clsx or classnames.

type NodeState = {
  isEditing: boolean;
  isDragging: boolean;
  isSelected: boolean;
  isSelectedStart: boolean;
  isSelectedEnd: boolean;
  isFocused: boolean;
  isOpen: boolean;
  isClosed: boolean;
  isLeaf: boolean;
  isInternal: boolean;
  willReceiveDrop: boolean;
};

Accessors

node.childIndex

Returns the node's index in relation to its siblings.

node.next

Returns the next visible node. The node directly under this node in the tree component. Returns null if none exist.

node.prev

Returns the previous visible node. The node directly above this node in the tree component. Returns null if none exist.

node.nextSibling

Returns the next sibling in the data of this node. Returns null if none exist.

Selection Methods

node.select()

Select only this node.

node.deselect()

Deselect this node. Other nodes may still be selected.

node.selectMulti()

Select this node while maintaining all other selections.

node.selectContiguous()

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().

Activation Methods

node.activate()

Runs the Tree props' onActivate callback passing in this node.

node.focus()

Focus this node.

Open/Close Methods

node.open()

Opens the node if it is an internal node.

node.close()

Closes the node if it is an internal node.

node.toggle()

Toggles the open/closed state of the node if it is an internal node.

node.openParents()

Opens all the parents of this node.

node.edit()

Moves this node into the editing state. Calling node.isEditing will return true.

node.submit(newName)

Submits newName string to the onRename handler. Moves this node out of the editing state.

node.reset()

Moves this node out of the editing state without submitting a new name.

Event Handlers

node.handleClick(event)

Useful for using the standard selection methods when a node is clicked. If the meta key is down, call multiSelect(). If the shift key is down, call selectContiguous(). Otherwise, call select() and activate().

Tree API Reference

The tree api reference is stable across re-renders. It always has the most recent state and props.

Node Accessors

tree.get(id) : NodeApi | null

Get node by id from the visibleNodes array.

tree.at(index) : NodeApi | null

Get node by index from the visibleNodes array.

tree.visibleNodes : NodeApi[]

Returns an array of the visible nodes.

tree.firstNode : NodeApi | null

The first node in the visibleNodes array.

tree.lastNode : NodeApi | null

The last node in the visibleNodes array.

tree.focusedNode : NodeApi | null

The currently focused node.

tree.mostRecentNode : NodeApi | null

The most recently selected node.

tree.nextNode : NodeApi | null

The node directly after the focusedNode in the visibleNodes array.

tree.prevNode : NodeApi | null

The node directly before the focusedNode in the visibleNodes array.

Focus Methods

tree.hasFocus : boolean

Returns true if the the tree has focus somewhere within it.

tree.focus(id)

Focus on the node with id.

tree.isFocused(id) : boolean

Check if the node with id is focused.

tree.pageUp()

Move focus up one page.

tree.pageDown()

Move focus down one page.

Selection Methods

tree.selectedIds : Set<string>

Returns a set of ids that are selected.

tree.selectedNodes : NodeApi[]

Returns an array of nodes that are selected.

tree.hasNoSelection : boolean

Returns true if nothing is selected in the tree.

tree.hasSingleSelection : boolean

Returns true if there is only one selection.

tree.hasMultipleSelections : boolean

Returns true if there is more than one selection.

tree.isSelected(id) : boolean

Returns true if the node with id is selected.

tree.select(id)

Select only the node with id.

tree.deselect(id)

Deselect the node with id.

tree.selectMulti(id)

Add to the selection the node with id.

tree.selectContiguous(id)

Deselected nodes between the anchor and the last selected node, then select the nodes between the anchor and the node with id.

tree.deselectAll()

Deselect all nodes.

tree.selectAll()

Select all nodes.

Visibility

tree.open(id)

Open the node with id.

tree.close(id)

Close the node with id.

tree.toggle(id)

Toggle the open state of the node with id.

tree.openParents(id)

Open all parents of the node with id.

tree.openSiblings(id)

Open all siblings of the node with id.

tree.openAll()

Open all internal nodes.

tree.closeAll()

Close all internal nodes.

tree.isOpen(id) : boolean

Returns true if the node with id is open.

Drag and Drop

tree.isDragging(id) : boolean

Returns true if the node with id is being dragged.

tree.willReceiveDrop(id) : boolean

Returns true if the node with id is internal and is under the dragged node.

Scrolling

tree.scrollTo(id, [align])

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".

Properties

tree.isEditing : boolean

Returns true if the tree is editing a node.

tree.isFiltered : boolean

Returns true if the searchTerm prop is not an empty string when trimmed.

tree.props : TreeProps

Returns all the props that were passed to the <Tree> component.

tree.root : NodeApi

Returns the root NodeApi instance. Its children are the Node representations of the data prop array.

Author

James Kerr at Brim Data for the Zui desktop app.

react-arborist's People

Contributors

aeolun avatar danielnilsson9 avatar gilbertsun avatar haines avatar hipstersmoothie avatar jackoliver avatar jameskerr avatar kemwalsh avatar marcbouchenoire avatar nickyvanurk avatar nvie avatar percy507 avatar rafma0 avatar rmeschian avatar sparekh0730 avatar vijayprasanna13 avatar wrobbinz 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  avatar  avatar  avatar  avatar  avatar  avatar

react-arborist's Issues

Remove unnecessary "height" prop, Support custom scroll bars

First of all, thanks for creating this package!

Currently, the tree only works with static widths and heights (using observers excluded).
However, since the tree height is already calculated in OuterElement in tree.tsx, I don't see why a static height would be required.

I did some quick tests - passing the calculated tree height to the enclosing position: relative div and changing the overflow values was already enough to get rid of the "height" prop requirement.

Bonus: Custom scroll bar libraries also started working correctly.

disabledDrop and target/source drop monitoring

Is there any facility in react-arborist to control dropping via source and target? More specifically can I limit dropping based on a combination of source key and target key? React DnD provides this via canDrop but disabledDrop is really just a static list. Thanks.

Feature request: alwaysOpen

I have a use-case where I never want to collapse the tree at any level. I just want a reorderable tree that's always expanded.

It would be good to allow a prop for this. Eg alwaysOpen / alwaysExpanded which did this.

It would also disable left/right arrow keys to expand/collapse branches)

Easy api to prevent nodes from being selectable

This is possible today, but maybe we could make an easier api.

Today, you'd make a custom RowRenderer and NodeRenderer. Then in there, you'd prevent selection if that node meets a certain criteria. An Tree Prop would make it all easier.

Tree.onSelect

It might be cleaner to have an onSelect callback that can be placed on the root tree node. It only get's fired when an item gets selected. A group can only be selected during multi select.

New Api:

type TreeProps = {
  onSelect: () // only get's called when a single item is selected.
}

Then when the tree selects something programmatically, this code can run and does not need to be duplicated.

Handler toggle is not working

Data

const treeData = {
  id: nanoid(),
  name: "Bookmarks",
  isOpen: true,
  children: [
    {
      id: nanoid(),
      name: "Brim Github",
      isOpen: true,
      children: [
        {
          id: nanoid(),
          name: "brim/pulls",
        },
        {
          id: nanoid(),
          name: "zed/pulls",
        },
        {
          id: nanoid(),
          name: "brim/releases",
        },
        {
          id: nanoid(),
          name: "brim/zson",
        },
        {
          id: nanoid(),
          name: "Level 3",
          isOpen: true,
          children: [
            { id: nanoid(), name: "amazon" },
            { id: nanoid(), name: "apple" },
            { id: nanoid(), name: "facebook" },
          ],
        },
      ],
    },
    {
      id: nanoid(),
      name: "Brim Zenhub",
      isOpen: true,
      children: [
        { id: nanoid(), name: "My Issues" },
        { id: nanoid(), name: "Brim All Issues" },
        { id: nanoid(), name: "MVP 0" },
        { id: nanoid(), name: "Manual Brim Test Cases" },
      ],
    },
    {
      id: nanoid(),
      name: "Meetings",
      isOpen: true,
      children: [
        { id: nanoid(), name: "Thursday" },
        { id: nanoid(), name: "Saturday" },
      ],
    },
    {
      id: nanoid(),
      name: "Personal",
      isOpen: true,
      children: [
        { id: nanoid(), name: "Imbox" },
        { id: nanoid(), name: "Facebook Marketplace" },
        { id: nanoid(), name: "Bank of America" },
        { id: nanoid(), name: "Mint" },
        { id: nanoid(), name: "Learn UI Design" },
      ],
    },
  ],
};

Element

function MaybeToggleButton({ toggle, isOpen, isFolder, isSelected }: any) {
  if (isFolder) {
    const Icon = isOpen ? treeMinusSmall : treePlusSmall;
    return (
      <button onClick={toggle} css={treeExpand}>
        <img src={Icon} alt="" />
      </button>
    );
  } else {
    return <div className="spacer" />;
  }
}

function Node({ innerRef, styles, data, state, handlers, tree }: any) {
  debugger;
  const folder = Array.isArray(data.children);
  const open = state.isOpen;
  const name = data.name;
  return (
    <div
      ref={innerRef}
      style={styles.row}
      className={clsx("tree-row", state)}
      onClick={(e) => handlers.select(e)}
      css={treeRow}
    >
      <div
        className="tree-row-contents"
        style={styles.indent}
        css={treeRowContents}
      >
        <MaybeToggleButton
          toggle={handlers.toggle}
          isOpen={open}
          isFolder={folder}
          isSelected={state.isSelected}
        />
        <div style={styles.indent}>{data.name}</div>
      </div>
    </div>
  );
}

Main Component

  <Tree
                          data={treeData}
                          hideRoot={true}
                          isOpen="isOpen"
                          getChildren="children"
                          indent={24}
                        >
                          {Node}
                        </Tree>

Access to tree height after rendering

Is there a way to get the tree's height? The only way I've found is by multiplying the rowHeight with tree.visibleNodes() - but the latter is only available inside the node renderer.
I'd like to use custom scroll bars with this package.

How can we use `renderContainer`?

Hi there,

I am trying to write a custom container for our tree but can't find any document on renderContainer? Just reading the code, the renderContainer provides no tree-api access, so not sure if it is even possible to have a custom one?

Thanks for the great works!

Cheers

README example renders nothing visible

I've created a new react apop using create-react-app and updated App.js with the example from the project's GitHub README so that I can start experimenting and learning, but the example given renders nothing for me. In the React Developer Tools, I can see the nodes are there, but I have no idea yet how to make them visible.

Shown below is what I see in the Components tab of the tools...

image

Following is my App.js...

import './App.css';
import { Tree } from "react-arborist";

const data = {
    id: "The Root",
    children: [{ id: "Node A" }, { id: "Node B" }]
}

function Node({ ref, styles, data }) {
    return (
        <div ref={ref} style={styles.row}>
            <div style={styles.indent}>
                {data.name}
            </div>
        </div>
    )
}

function App() {
    return (
        <div className="App">
            <div className="workspace-leaf-content" data-type="collection-manager">
                <div className="view-content">
                    
                    <h4>React Arborist Tree Component Test</h4>
                    
                    <Tree data={data}>{Node}</Tree>

                </div>
            </div>
        </div>
    );
}

export default App;

But, as you can see, nothing renders...

image

I am guessing the README just assumes one knows more than I do about React or something. I could use a hint! Or do you sell consulting hours?

Demo with Dynamically Loaded Children

This should be possible with the current api, but it would be good to write a demo. If anyone has already done this, please post the code here! Thanks.

Error with multiple trees

If you have 2 Tree components rendering at the same time a React Maximum update depth exceeded error is thrown. The line below is causing the error.

useLayoutEffect(() => {
    // @ts-ignore
    dispatch(actions.setVisibleIds(api.visibleIds, api.idToIndex));  // <- causes the error.
  }, [dispatch, api.visibleIds, api.idToIndex, props.root]);

Here is a codesandbox with a basic repro.

https://codesandbox.io/s/react-arborist-forked-xb7ezj

Thanks!

Configurable keyboard shortcuts

All the keyboard shortcuts are listed in the DefaultContainer component. I could see how people would want to use their own keybindings.

Something like:

{
  "a": "createLeaf",
  "shift+a": "createInternal"
  "space": "activate",
  "meta+DownArrow": "activate"
}

Guidance on how to make a node draggable.

While I've implemented the tree just fine, I'm stuck on how to make something actually draggable.

I tried adding isDraggable/isDroppable to the data nodes. No luck.

Any thoughts?

Demo Budget Tree

It'd be nice to have a demo where you have folders of expenses and each expense amount is on the right. The folders would sum the children and display it on the right.

Dragging around would update folder amounts like usual react stuff.

Demo or code example of tree structure with connection lines

Hey the component is really cool but I would like to ask for an example with connection line (horizontal and vertical).
Is it possible to provide code example or add it to the api?
I tried doing it with css and custom render of the node but got confused.
Tnx!

Reorder with Tree.data set?

How does one drag/drop with the Tree.data attribute set (instead of the initialData). There are no drag and drop callbacks. Am I missing something? Also I would prefer to just use initialData, let the tree handle everything like it does by default, and have callbacks for when I add/edit/delete/drag nodes so I can update my database. Right now this isn't possible and I have to re-implement a lot of functionality in the callbacks because the callbacks don't work with initialData set.

How to get the current scroll position and the overall height of the scroll container?

I want to make a tree which would load data asynchronously upon user opening a tree node (which seems easy to do here) or upon scrolling down the tree root (in my use case root would have not only folders, but also lots and lots of items (thoughsands of them) and it wouldn't be a great experience for a user to load them all at once).
Is there a way to get the overall height of the scroll container and current scroll position to make the latter possible? Or if not, is there a better way to do what I need?

Why not use `renderer` prop on `Tree` for NodeRenderer?

I'm probably missing something, but it seems a little strange to me that the Tree component takes data for its children, rather than the more typical approach of children nodes being content to be passed in to a component. It seems like a more common approach would be to pass the node renderer as a prop to Tree. Which would probably mean that Tree would be a self-closing tag/component.

I notice here that the main Tree component implementation does, indeed, assign children to the renderer prop of TreeViewProvider, which appears to be the real workhorse component.

Suggestion:

  • create a renderer prop on Tree
  • deprecate use of Tree's children

Document onClick and onContextMenu props in the tree.

We need to document the Tree's onClick and onContextMenu props. Those are attached to the tree container so that you can take action when the user clicks or rightclicks on an area of the tree that has no items. Maybe you clear the selection, or provide a different context menu.

Expose onSelect event to Tree component

Hi James,

I need to listen for the tree node select event via the Tree component.
Would it be possible to add this?

My use case is I'm using check-boxes to select multiple tree nodes using the handlers.select which has shift-click but I only want one event to fire.

Kind regards,
Marten

[Feature request] Ctrl + Z

Hello! I'm trying the demo and amazing package! The only thing i'm missing is an historic and a ctrl + Z, in case you did not have it already in mind!

I will try the package and if i end up using it I migth try to build that feature myself if you accept contributors :)

Cheers for the project!

accessor is not available

The docs say there's childrenAccessor and isOpenAccessor but I only see getChildren in the code ๐Ÿค” is this upcoming API?

External selection mgmt

This works pretty well. I've been comparing this package to a bunch of other react trees, and I am most hopeful this will work for my use case.

One thing I find strange is I can only access selection state from inside the Node renderer. Why not expose onSelect the same as you do with onEdit and onToggle, to make it easier for syncing with the selection state of the tree?

Related, you added an onClick prop (that isn't listed on the documentation page) and I'm wondering if that was meant to make it possible to configure external selection state synching? It fires the synthetic click event and not some computed state object so I'd be we wary using that to try and set up outside mgmt.

Am I missing something?

Module parse failed: Unexpected token (394:7)

Failed to compile.

./node_modules/react-arborist/dist/module.js 394:7
Module parse failed: Unexpected token (394:7)
File was processed with these loaders:
 * ./node_modules/react-scripts/node_modules/babel-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
|     };
| 
>     el?.addEventListener("keydown", cb);
|     return () => {
|       el?.removeEventListener("keydown", cb);

Add API & DND support for moving nodes + hierarchy to a separate tree instance

We need to have two tree instances which allow users to move nodes (complete with their hierarchy) between them.

For the API, we'd have a button that onClick, moves all selected nodes from tree1 to tree2. The node will disappear from tree1, and appear in tree2. If it's parent nodes have no other children, they too will disappear. If any parent nodes already existed in tree2, they will merge.

Drag and drop would use the same move logic, it would just occur when a user drags a node.

I don't see any clear support for this yet.

Drag Handle

Love what you have made!!

Learning so much by reading the source. Fantastic job.

I would love a drag handle ref option.

packages/react-arborist/src/components/row.tsx maybe expose optionally the dragRef ?

Thanks!

Selected Ids Property

We need a way to mount the Tree component with a selection already in place.

Idea 1: I think a prop called initialSelectedIds would be a good option.

Idea 2: Or we do nothing and leave this to the user. They can get a tree ref, then use an effect for each time their "selection" criteria changes. Then use the tree's api to select that id.

If a meta key is held, don't toggle the group.

If we create a simple handlers.onClick function, it should check if the meta key is held. If so, it needs to select the folder, not toggle it. Otherwise it toggles it.

If we create an onSelect property, it should not run if the meta keys are held down.

Add keyboard interaction and ARIA roles

I saw your post on reddit. Nice tree control.

I wanted to give a suggestion that could help take it to the next level. Right now it is very mouse oriented. However, not everyone may prefer to use the mouse. If you look up the the tree role on MDN it documents keyboard interactions for a tree. That way users can operate it without taking their hands off the keyboard if they prefer (similar to Ctrl-C Ctrl-V for copy and paste).

How to move a node to the child of a leaf node?

Hello,

I'm looking for a way to move a node to the child of the other leaf node.

Currently, I can move nodes into exisiting folder nodes (it means they have children already)

Please help me!

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.