Skip to content

Commit 38d6c95

Browse files
committed
fix: rewrote onDrop handler to account for insertion order issues (#353)
1 parent 8701b0c commit 38d6c95

File tree

3 files changed

+74
-57
lines changed

3 files changed

+74
-57
lines changed

next-release-notes.md

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
<!--
2-
### Breaking Changes
3-
4-
### Features
5-
61
### Bug Fixes and Improvements
7-
8-
### Other Changes
9-
-->
2+
- Fixed a bug where items where sometimes inserted with inverted order. (#353)
3+
- The `onDrop` should be a bit faster in general, since some redundant calls to `dataProvider.onChangeItemChildren` have been removed. In the past, this was called twice per item, once for moving the item out of its old folder, and once for moving it into the target folder. Now, all insertion calls into the target folder are batched into one `dataProvider.onChangeItemChildren` call for all dragged items.

packages/core/src/uncontrolledEnvironment/UncontrolledTreeEnvironment.tsx

+54-49
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,11 @@ export const UncontrolledTreeEnvironment = React.forwardRef<
126126
}}
127127
onDrop={async (items, target) => {
128128
const promises: Promise<void>[] = [];
129+
const itemsIndices = items.map(i => i.index);
130+
let itemsPriorToInsertion = 0;
129131

130-
// when dropped between items, items are injected at top of insertion point each
131-
const orderedItems =
132-
target.targetType === 'between-items' ? [...items].reverse() : items;
133-
134-
for (const item of orderedItems) {
132+
// move old items out
133+
for (const item of items) {
135134
const parent = Object.values(currentItems).find(potentialParent =>
136135
potentialParent.children?.includes(item.index)
137136
);
@@ -146,67 +145,73 @@ export const UncontrolledTreeEnvironment = React.forwardRef<
146145
);
147146
}
148147

149-
if (target.targetType === 'item' || target.targetType === 'root') {
150-
if (target.targetItem === parent.index) {
151-
// NOOP
152-
} else {
153-
promises.push(
154-
dataProvider.onChangeItemChildren(
155-
parent.index,
156-
parent.children.filter(child => child !== item.index)
157-
)
158-
);
159-
promises.push(
160-
dataProvider.onChangeItemChildren(target.targetItem, [
161-
...(currentItems[target.targetItem].children ?? []),
162-
item.index,
163-
])
164-
);
165-
}
166-
} else {
167-
const newParent = currentItems[target.parentItem];
168-
const newParentChildren = [...(newParent.children ?? [])].filter(
169-
child => child !== item.index
170-
);
148+
if (
149+
target.targetType === 'between-items' &&
150+
target.parentItem === item.index
151+
) {
152+
// Trying to drop inside itself
153+
return;
154+
}
171155

172-
if (target.parentItem === item.index) {
173-
// Trying to drop inside itself
174-
return;
175-
}
156+
if (
157+
(target.targetType === 'item' || target.targetType === 'root') &&
158+
target.targetItem !== parent.index
159+
) {
160+
promises.push(
161+
dataProvider.onChangeItemChildren(
162+
parent.index,
163+
parent.children.filter(child => child !== item.index)
164+
)
165+
);
166+
}
176167

168+
if (target.targetType === 'between-items') {
177169
if (target.parentItem === parent.index) {
170+
const newParent = currentItems[target.parentItem];
178171
const isOldItemPriorToNewItem =
179172
((newParent.children ?? []).findIndex(
180173
child => child === item.index
181174
) ?? Infinity) < target.childIndex;
182-
newParentChildren.splice(
183-
target.childIndex - (isOldItemPriorToNewItem ? 1 : 0),
184-
0,
185-
item.index
186-
);
187-
promises.push(
188-
dataProvider.onChangeItemChildren(
189-
target.parentItem,
190-
newParentChildren
191-
)
192-
);
175+
itemsPriorToInsertion += isOldItemPriorToNewItem ? 1 : 0;
193176
} else {
194-
newParentChildren.splice(target.childIndex, 0, item.index);
195177
promises.push(
196178
dataProvider.onChangeItemChildren(
197179
parent.index,
198180
parent.children.filter(child => child !== item.index)
199181
)
200182
);
201-
promises.push(
202-
dataProvider.onChangeItemChildren(
203-
target.parentItem,
204-
newParentChildren
205-
)
206-
);
207183
}
208184
}
209185
}
186+
187+
// insert new items
188+
if (target.targetType === 'item' || target.targetType === 'root') {
189+
promises.push(
190+
dataProvider.onChangeItemChildren(target.targetItem, [
191+
...(currentItems[target.targetItem].children ?? []).filter(
192+
i => !itemsIndices.includes(i)
193+
),
194+
...itemsIndices,
195+
])
196+
);
197+
} else {
198+
const newParent = currentItems[target.parentItem];
199+
const newParentChildren = [...(newParent.children ?? [])].filter(
200+
c => !itemsIndices.includes(c)
201+
);
202+
newParentChildren.splice(
203+
target.childIndex - itemsPriorToInsertion,
204+
0,
205+
...itemsIndices
206+
);
207+
promises.push(
208+
dataProvider.onChangeItemChildren(
209+
target.parentItem,
210+
newParentChildren
211+
)
212+
);
213+
}
214+
210215
await Promise.all(promises);
211216
props.onDrop?.(items, target);
212217
}}

packages/core/test/dnd-basics.spec.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,24 @@ describe('dnd basics', () => {
219219
]);
220220
});
221221

222+
it('drag items with scrambled order below in same folder', async () => {
223+
const test = await new TestUtil().renderOpenTree();
224+
await test.selectItems('aac', 'aaa', 'aab');
225+
await test.startDrag('aab');
226+
await test.dragOver('aad', 'bottom');
227+
await test.drop();
228+
await test.expectVisibleItemContents('aa', ['aad', 'aaa', 'aab', 'aac']);
229+
});
230+
231+
it('drag items with scrambled order above in same folder', async () => {
232+
const test = await new TestUtil().renderOpenTree();
233+
await test.selectItems('aac', 'aad', 'aab');
234+
await test.startDrag('aab');
235+
await test.dragOver('aaa', 'top');
236+
await test.drop();
237+
await test.expectVisibleItemContents('aa', ['aab', 'aac', 'aad', 'aaa']);
238+
});
239+
222240
it('drag folders into another opened folder', async () => {
223241
const test = await new TestUtil().renderOpenTree();
224242
await test.selectItems('ab', 'ac', 'ad');

0 commit comments

Comments
 (0)