react-complex-tree
react-complex-tree copied to clipboard
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?
Hi @dberardo-com!
I'm not sure what exactly you mean, do you want to find out whether a item is selected? That would be the property context.isSelected. The method for rendering an item in the core package is defined here: https://github.com/lukasbach/react-complex-tree/blob/main/packages/core/src/renderers/createDefaultRenderers.tsx#L86
So you can reproduce the default rendering behaviour like this:
{
renderItem: ({ item, depth, children, title, context, arrow }) => {
const InteractiveComponent = context.isRenaming ? 'div' : 'button';
return (
<li
{...(context.itemContainerWithChildrenProps as any)}
className={cx(
'rct-tree-item-li',
item.hasChildren && 'rct-tree-item-li-hasChildren',
context.isSelected && 'rct-tree-item-li-selected',
context.isExpanded && 'rct-tree-item-li-expanded',
context.isFocused && 'rct-tree-item-li-focused',
context.isDraggingOver && 'rct-tree-item-li-dragging-over',
context.isSearchMatching && 'rct-tree-item-li-search-match'
)}
>
<div
{...(context.itemContainerWithoutChildrenProps as any)}
style={{ paddingLeft: `${(depth + 1) * (renderers.renderDepthOffset ?? 10)}px` }}
className={cx(
'rct-tree-item-title-container',
item.hasChildren && 'rct-tree-item-title-container-hasChildren',
context.isSelected && 'rct-tree-item-title-container-selected',
context.isExpanded && 'rct-tree-item-title-container-expanded',
context.isFocused && 'rct-tree-item-title-container-focused',
context.isDraggingOver && 'rct-tree-item-title-container-dragging-over',
context.isSearchMatching && 'rct-tree-item-title-container-search-match'
)}
>
{arrow}
<InteractiveComponent
{...(context.interactiveElementProps as any)}
className={cx(
'rct-tree-item-button',
item.hasChildren && 'rct-tree-item-button-hasChildren',
context.isSelected && 'rct-tree-item-button-selected',
context.isExpanded && 'rct-tree-item-button-expanded',
context.isFocused && 'rct-tree-item-button-focused',
context.isDraggingOver && 'rct-tree-item-button-dragging-over',
context.isSearchMatching && 'rct-tree-item-button-search-match'
)}
>
{title}
</InteractiveComponent>
</div>
{children}
</li>
);
},
}
yes, this is what i meant. I would like to use Material UI as basis to style the sidebar, thus i would like to override some classes directly from Javascript.
The only catch in the code above is "where can i get the renderers from within the renderItem callback?
i have just noticed that using this style object:
style={{ paddingLeft:${(depth + 1) * 10}px, }}
without the call to the renderers object seems to lead to the same default behavior. so that would be enough for my use case i guess
The renders prop contains the render logic, if you write your own renderers, you define those values yourself. The variable renderDepthOffset just defaults to the number 4, so if that's all you need, you can just inline the number or use a custom one as you mentioned. You can also use the default renderers by calling createDefaultRenderers({}) anywhere.
is it possible to pass a reference to the custom renderer to the tree item that the current node represents?
what i am trying to achieve here is to open a menu on right click of a tree node and use a renameNode callback when the specific context menu item is selected.
I am having problem firing the "startRenamingItem" method from the current reference to the root tree. So my thought was to handle it directly like we do in the custom interaction object, i.e. accessing the "actions" object and using it in the custom renderer.
Maybe i should document that in a separate issue, but if i pass the TreeRef and call the startRenamingItem method from the ref, i get to see that only the onAbortRenamingItem is called, and not all other on* methods
Sorry, but I'm not completely sure what the problem is. In the render method of the item, one of the props should be a startRenamingItem method that you should be able to directly call (https://rct.lukasbach.com/docs/api/interfaces/treeitemrendercontext/#startrenamingitem). But using the imperative ref to the tree should also work...
hi @lukasbach i have been experimenting with the complex-tree library and i have managed to create a custom ListItem that reacts to right clicks using the onContextMenu handler and displays a context menu:

now, i have 2 problems here:
1 - the underlying tree node of the sidebar is still selected and it is still listening to onclick events, so each time i click on a field in the contextmenu the event gets bubbled to the selected item, and this is definitely not what i want
2 - if i try to add a Rename Item field in the context menu and use the startRenaimingItem function, that will not work, i think for the same reason as in (1), i.e. that the renaming action is called immediately, perhaps because the selectedItem is "clicked" again
how can i force the selectedItem to still be focused (visually, so the user knows it has been selected) but disconnecting it from the onclick event listener?
just as extra information:
- most of the component in the sidebar come from MaterialUI v4
- i have tried adding event.stopPropagation() in the MenuItem onClick handler, but that does not prevent the eventListener on the sidebar node to fire
- the MenuItem is a DOM child of the sidebar node (thats why the event bubbles to it)
on problem number 2: this is the log i get when trying to start renaming the item:

right after that i see the log of the renamedAborted callback
To avoid bubbling up click events from the context menu to the item renderer, I would first try to call e.stopPropagation() directly in the handler of the click events on the menu items, that should avoid the event bubbling altogether. If that doesn't work, you can try to implement a custom interaction manager that ignores events when the menu is open.
Regarding the form element being a descendant of a p element: If you start renaming, the item is rendererd in a form container. Looks like some material component you use for something further up renders in a paragraph element, maybe you can prevent that further up. In an accessible tree, you shouldn't have any paragraph elements further up in the tree structure anyways.
I'm having the same issue here, onFocusItem is being fired every time you trigger Drag, Drop or createInteractiveElementProps: onContextMenu and is being fired before them, so e.stopPropagation() won't work