Skip to content

Commit 1ba8f01

Browse files
authored
[5574] - add moveBefore and moveAfter to useTreeData (#7689)
* [5574] - add moveBefore and moveAfter to useTreeData * add docs * remove onlys * remove console logs
1 parent 016590a commit 1ba8f01

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

Diff for: packages/@react-stately/data/docs/useTreeData.mdx

+27
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,33 @@ tree.move('Sam', 'Animals', 1);
182182
tree.move('Sam', null, 1);
183183
```
184184

185+
### Move before
186+
An alias to move
187+
188+
```tsx
189+
// Move an item within the same parent
190+
tree.moveBefore('Sam', 'People', 0);
191+
192+
// Move an item to a different parent
193+
tree.moveBefore('Sam', 'Animals', 1);
194+
195+
// Move an item to the root
196+
tree.moveBefore('Sam', null, 1);
197+
```
198+
199+
### Move after
200+
201+
```tsx
202+
// Move an item within the same parent
203+
tree.moveAfter('Sam', 'People', 0);
204+
205+
// Move an item to a different parent
206+
tree.moveAfter('Sam', 'Animals', 1);
207+
208+
// Move an item to the root
209+
tree.moveAfter('Sam', null, 1);
210+
```
211+
185212
### Updating items
186213

187214
```tsx

Diff for: packages/@react-stately/data/src/useTreeData.ts

+60
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,22 @@ export interface TreeData<T extends object> {
107107
*/
108108
move(key: Key, toParentKey: Key | null, index: number): void,
109109

110+
/**
111+
* Moves an item before a node within the tree.
112+
* @param key - The key of the item to move.
113+
* @param toParentKey - The key of the new parent to insert into. `null` for the root.
114+
* @param index - The index within the new parent to insert before.
115+
*/
116+
moveBefore(key: Key, toParentKey: Key | null, index: number): void,
117+
118+
/**
119+
* Moves an item after a node within the tree.
120+
* @param key - The key of the item to move.
121+
* @param toParentKey - The key of the new parent to insert into. `null` for the root.
122+
* @param index - The index within the new parent to insert after.
123+
*/
124+
moveAfter(key: Key, toParentKey: Key | null, index: number): void,
125+
110126
/**
111127
* Updates an item in the tree.
112128
* @param key - The key of the item to update.
@@ -373,6 +389,50 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
373389
}), newMap);
374390
});
375391
},
392+
moveBefore(key: Key, toParentKey: Key | null, index: number) {
393+
this.move(key, toParentKey, index);
394+
},
395+
moveAfter(key: Key, toParentKey: Key | null, index: number) {
396+
setItems(({items, nodeMap: originalMap}) => {
397+
let node = originalMap.get(key);
398+
if (!node) {
399+
return {items, nodeMap: originalMap};
400+
}
401+
402+
let {items: newItems, nodeMap: newMap} = updateTree(items, key, () => null, originalMap);
403+
404+
const movedNode = {
405+
...node,
406+
parentKey: toParentKey
407+
};
408+
409+
const afterIndex = items.length === index ? index : index + 1;
410+
// If parentKey is null, insert into the root.
411+
if (toParentKey == null) {
412+
newMap.set(movedNode.key, movedNode);
413+
return {items: [
414+
...newItems.slice(0, afterIndex),
415+
movedNode,
416+
...newItems.slice(afterIndex)
417+
], nodeMap: newMap};
418+
}
419+
420+
// Otherwise, update the parent node and its ancestors.
421+
return updateTree(newItems, toParentKey, parentNode => {
422+
const c = [
423+
...parentNode.children!.slice(0, afterIndex),
424+
movedNode,
425+
...parentNode.children!.slice(afterIndex)
426+
];
427+
return {
428+
key: parentNode.key,
429+
parentKey: parentNode.parentKey,
430+
value: parentNode.value,
431+
children: c
432+
};
433+
}, newMap);
434+
});
435+
},
376436
update(oldKey: Key, newValue: T) {
377437
setItems(({items, nodeMap: originalMap}) => updateTree(items, oldKey, oldNode => {
378438
let node: TreeNode<T> = {

Diff for: packages/@react-stately/data/test/useTreeData.test.js

+70
Original file line numberDiff line numberDiff line change
@@ -676,4 +676,74 @@ describe('useTreeData', function () {
676676
expect(result.current.items[1].value).toEqual(initialResult.items[2].value);
677677
expect(result.current.items[2]).toEqual(initialResult.items[1]);
678678
});
679+
680+
681+
it('should move an item within its same level before the target', function () {
682+
const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}];
683+
684+
let {result} = renderHook(() =>
685+
useTreeData({initialItems, getChildren, getKey})
686+
);
687+
act(() => {
688+
result.current.moveBefore('Eli', null, 0);
689+
});
690+
expect(result.current.items[0].key).toEqual('Eli');
691+
expect(result.current.items[1].key).toEqual('David');
692+
expect(result.current.items[2].key).toEqual('Emily');
693+
expect(result.current.items.length).toEqual(initialItems.length);
694+
});
695+
696+
it('should move an item to a different level before the target', function () {
697+
const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}];
698+
699+
let {result} = renderHook(() =>
700+
useTreeData({initialItems, getChildren, getKey})
701+
);
702+
act(() => {
703+
result.current.moveBefore('Eli', 'David', 1);
704+
});
705+
expect(result.current.items[0].key).toEqual('David');
706+
expect(result.current.items[0].children[0].key).toEqual('John');
707+
expect(result.current.items[0].children[1].key).toEqual('Eli');
708+
expect(result.current.items[1].key).toEqual('Emily');
709+
expect(result.current.items.length).toEqual(2);
710+
});
711+
712+
it('should move an item to a different level after the target', function () {
713+
const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}];
714+
let {result} = renderHook(() =>
715+
useTreeData({initialItems, getChildren, getKey})
716+
);
717+
718+
act(() => {
719+
result.current.moveAfter('Eli', 'David', 1);
720+
});
721+
expect(result.current.items[0].key).toEqual('David');
722+
723+
expect(result.current.items[0].children[0].key).toEqual('John');
724+
expect(result.current.items[0].children[1].key).toEqual('Sam');
725+
expect(result.current.items[0].children[2].key).toEqual('Eli');
726+
expect(result.current.items[1].key).toEqual('Emily');
727+
expect(result.current.items.length).toEqual(2);
728+
});
729+
730+
it('should move an item to a different level at the end when the index is greater than the node list length', function () {
731+
const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}];
732+
console.log('initialItems', initialItems[0]);
733+
let {result} = renderHook(() =>
734+
useTreeData({initialItems, getChildren, getKey})
735+
);
736+
737+
act(() => {
738+
result.current.moveAfter('Eli', 'David', 100);
739+
});
740+
expect(result.current.items[0].key).toEqual('David');
741+
742+
expect(result.current.items[0].children[0].key).toEqual('John');
743+
expect(result.current.items[0].children[1].key).toEqual('Sam');
744+
expect(result.current.items[0].children[2].key).toEqual('Jane');
745+
expect(result.current.items[0].children[3].key).toEqual('Eli');
746+
expect(result.current.items[1].key).toEqual('Emily');
747+
expect(result.current.items.length).toEqual(2);
748+
});
679749
});

0 commit comments

Comments
 (0)