Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Is it possible for items to have dynamic height? #66

Unanswered
sphillips asked this question in Q&A
Discussion options

Hello. I'm really enjoying using this library, thanks for the work you've put into it!

I'm wondering if it's at all possible to use this with row items which have dynamic height. For example, I'm building a data table view which has some content in a field that may wrap to multiple lines. I noticed all of the examples contain parents and children of the same component with uniform height, and that the height is hard-coded in the CSS.

In my implementation I am also trying to add padding to the parent items. If I apply any extra padding to the parent item or allow the child items to have dynamic height, and I try to drag a child item to re-order, the drag line has some offset padding so it appears below where it should.

As far as I can tell this isn't easily overridden, but please let me know if you have any suggestions. Thanks!

Screen Shot 2022年04月20日 at 5 35 04 PM

You must be logged in to vote

Replies: 1 comment 2 replies

Comment options

Hi, unfortunately trees with items of varying items is not possible with this library if you need drag-and-drop and is not part of the immediate scope, altough I would maybe like to support it sometime in the future. If you don't want to use drag-and-drop capabilities, it should work for varying item heights.

You must be logged in to vote
2 replies
Comment options

So I wanted this library to adhere to this specific case even though the internals don't allow it. I dig up that getHoveringPosition and renderDragBetweenLine are responsible for handling this. Now you can override renderDragBetweenLine but not the getHoveringPosition which takes the itemHeight of the first tree item and uses it to calculate the drag line position for subsequent ones. So I paired-program with Claude 3 Sonnet for this one. Ultimately with some trial and errors, we came up with this :-

var getHoveringPosition = function (clientY, treeTop, itemHeight, linearItems, treeId, items, canDropOnFolder, canDropOnNonFolder, canReorderItems, getItemLayout = 
 (item)=>{
 var itemId = item.index.toString();
 var element = document.querySelector(`[data-rct-item-id="${itemId}"]`)
 return{
 top: element.getBoundingClientRect().top,
 height: element.offsetHeight
 }}) {
 var hoveringPosition = clientY;
 var treeLinearItems = linearItems[treeId];
 var targetLinearItem;
 var targetItem;
 var targetItemLayout;
 for (var i = 0; i < treeLinearItems.length; i++) {
 var currentLinearItem = treeLinearItems[i];
 var currentItem = items[currentLinearItem.item];
 var currentItemLayout = getItemLayout(currentItem);
 var nextItem = items[treeLinearItems[i + 1]?.item];
 var nextItemLayout = nextItem ? getItemLayout(nextItem) : null;
 var spaceBetweenNextAndCurrent = nextItemLayout ? nextItemLayout.top - (currentItemLayout.top + currentItemLayout.height) : 0;
 if (spaceBetweenNextAndCurrent > 0) {
 currentItemLayout.height += spaceBetweenNextAndCurrent;
 }
 if (hoveringPosition >= currentItemLayout.top && hoveringPosition < currentItemLayout.top + currentItemLayout.height) {
 targetLinearItem = currentLinearItem;
 targetItem = currentItem;
 targetItemLayout = currentItemLayout;
 break;
 }
 }
 const lastItem = items[treeLinearItems[treeLinearItems.length - 1].item];
 const lastItemLayout = getItemLayout(lastItem);
 if(hoveringPosition >= lastItemLayout.top + lastItemLayout.height){
 return {
 linearIndex: treeLinearItems.length - 1,
 offset: 'bottom',
 veryBottom: true,
 };
 }
 if (!targetItemLayout) {
 return {
 linearIndex: -1,
 offset: 'bottom',
 veryBottom: false,
 };
 }
 var lineThreshold = !canReorderItems
 ? 0
 : ((targetItem === null || targetItem === void 0 ? void 0 : targetItem.isFolder) && canDropOnFolder) || canDropOnNonFolder
 ? 0.2
 : 0.5;
 var relativePosition = (hoveringPosition - targetItemLayout.top) / targetItemLayout.height;
 if (relativePosition < lineThreshold) {
 return { linearIndex: treeLinearItems.indexOf(targetLinearItem), offset: 'top' };
 } else if (relativePosition > 1 - lineThreshold) {
 return { linearIndex: treeLinearItems.indexOf(targetLinearItem), offset: 'bottom' };
 } else {
 return { linearIndex: treeLinearItems.indexOf(targetLinearItem), offset: undefined };
 }
};

treeTop and itemHeight are not used in this but left here to not break the code where this function is called from.

This definitely would break the library if you have some other use case than rendering stuff for one tree. I haven't even tested that. This solved the problem for me. Not performant because it's querying the dom elements coordinates a no of times but yeah works accurately.

In the existing getHoveringPosition - there is no concept of factoring in dynamic item height of each tree item and also any space between them. This one does both by relying on the data-rct-item-id data-attribute and querying elements.

Now just doing above won't be enough because renderDragBetweenLine internally still uses the constant itemHeight. But we can override it from the ControlledTreeEnvironment provider. I have used fixed positioning there to get this working for me accurately. In fact, if you don't change the getHoveringPosition and just make renderDragBetweenLine work with fixed positioning, it will seem like it's working but with then you start seeing the offset between where your cursor is and where the dragLine appears and it only increases with more items and variability and spacing. That's why both the internal getHoveringPosition and renderDragBetweenLine need to work together.

 renderDragBetweenLine={({ draggingPosition, lineProps }) => {
 const isBetweenItems = draggingPosition.targetType === 'between-items';
 if (isBetweenItems) {
 const parentItemId = draggingPosition.parentItem.toString() ?? guideSlug;
 const { childIndex } = draggingPosition;
 const treeItems = longTree.items;
 const parentTreeItem = treeItems[parentItemId];
 const parentTreeItemChildren = parentTreeItem?.children;
 const itemId = parentTreeItemChildren?.[childIndex];
 if (!itemId) {
 const lastItemOfCurrentParent = parentTreeItemChildren?.[childIndex - 1];
 if (!lastItemOfCurrentParent) return null;
 const lastItemId = lastItemOfCurrentParent.toString();
 const itemElement = document
 .querySelector(`[data-rct-item-id="${lastItemId.toString()}"]`)
 ?.closest('li');
 const top = itemElement?.getBoundingClientRect().bottom;
 return (
 <div
 {...lineProps}
 css={tw`bg-blue-600 dark:bg-blue-500`}
 style={{
 position: 'fixed',
 right: '0',
 top,
 left: `${draggingPosition.depth * 23}px`,
 height: '4px',
 }}
 />
 );
 }
 const itemElement = document.querySelector(`[data-rct-item-id="${itemId.toString()}"]`)?.closest('li');
 const top = itemElement?.getBoundingClientRect().top;
 return (
 <div
 {...lineProps}
 css={tw`bg-blue-600 dark:bg-blue-500`}
 style={{
 position: 'fixed',
 right: '0',
 top,
 left: `${draggingPosition.depth * 23}px`,
 height: '4px',
 }}
 />
 );
 }
 return null;
 }}

Again this is very very hacky and suits my needs. Since I was building a fast prototype I have resorted to this hack but would probably switch to something like Pragmatic DND to rewrite everything from ground-up if the prototype gets validation. Wouldn't go for a rewrite route if the lib itself starts supporting it because it's really great.

@lukasbach - Btw great library mate. The baked in accessibility and options to control a lot of things from the provider is very helpful 💯 ✨ .

Note :- The version being used is 2.3.4

Comment options

@lukasbach any chance you’ve provided support for this now?

Googles Ai suggested you had a getitemheight prop, but doesn’t seem like it actually exists hahaha

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /