react-complex-tree icon indicating copy to clipboard operation
react-complex-tree copied to clipboard

Possibility to use the same default classes in custom renderers

Open dberardo-com opened this issue 4 years ago • 11 comments
trafficstars

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?

dberardo-com avatar Oct 28 '21 08:10 dberardo-com

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>
      );
    },
  }

lukasbach avatar Oct 28 '21 17:10 lukasbach

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?

dberardo-com avatar Nov 02 '21 16:11 dberardo-com

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

dberardo-com avatar Nov 02 '21 16:11 dberardo-com

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.

lukasbach avatar Nov 07 '21 21:11 lukasbach

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

dberardo-com avatar Nov 08 '21 16:11 dberardo-com

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

lukasbach avatar Nov 09 '21 17:11 lukasbach

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:

image

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?

dberardo-com avatar Dec 01 '21 12:12 dberardo-com

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)

dberardo-com avatar Dec 01 '21 13:12 dberardo-com

on problem number 2: this is the log i get when trying to start renaming the item:

image

right after that i see the log of the renamedAborted callback

dberardo-com avatar Dec 01 '21 13:12 dberardo-com

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.

lukasbach avatar Apr 23 '22 23:04 lukasbach

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

chalscc avatar Aug 10 '22 07:08 chalscc