-
-
Notifications
You must be signed in to change notification settings - Fork 94
-
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!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 2 replies
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
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
Beta Was this translation helpful? Give feedback.
All reactions
-
@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
Beta Was this translation helpful? Give feedback.