@@ -107,6 +107,20 @@ export interface TreeData<T extends object> {
107
107
*/
108
108
move ( key : Key , toParentKey : Key | null , index : number ) : void ,
109
109
110
+ /**
111
+ * Moves one or more items before a given key.
112
+ * @param key - The key of the item to move the items before.
113
+ * @param keys - The keys of the items to move.
114
+ */
115
+ moveBefore ( key : Key , keys : Iterable < Key > ) : void ,
116
+
117
+ /**
118
+ * Moves one or more items after a given key.
119
+ * @param key - The key of the item to move the items after.
120
+ * @param keys - The keys of the items to move.
121
+ */
122
+ moveAfter ( key : Key , keys : Iterable < Key > ) : void ,
123
+
110
124
/**
111
125
* Updates an item in the tree.
112
126
* @param key - The key of the item to update.
@@ -115,6 +129,11 @@ export interface TreeData<T extends object> {
115
129
update ( key : Key , newValue : T ) : void
116
130
}
117
131
132
+ interface TreeDataState < T extends object > {
133
+ items : TreeNode < T > [ ] ,
134
+ nodeMap : Map < Key , TreeNode < T > >
135
+ }
136
+
118
137
/**
119
138
* Manages state for an immutable tree data structure, and provides convenience methods to
120
139
* update the data over time.
@@ -128,7 +147,7 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
128
147
} = options ;
129
148
130
149
// We only want to compute this on initial render.
131
- let [ tree , setItems ] = useState < { items : TreeNode < T > [ ] , nodeMap : Map < Key , TreeNode < T > > } > ( ( ) => buildTree ( initialItems , new Map ( ) ) ) ;
150
+ let [ tree , setItems ] = useState < TreeDataState < T > > ( ( ) => buildTree ( initialItems , new Map ( ) ) ) ;
132
151
let { items, nodeMap} = tree ;
133
152
134
153
let [ selectedKeys , setSelectedKeys ] = useState ( new Set < Key > ( initialSelectedKeys || [ ] ) ) ;
@@ -141,7 +160,7 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
141
160
items : initialItems . map ( item => {
142
161
let node : TreeNode < T > = {
143
162
key : getKey ( item ) ,
144
- parentKey : parentKey ,
163
+ parentKey : parentKey ?? null ,
145
164
value : item ,
146
165
children : null
147
166
} ;
@@ -154,9 +173,9 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
154
173
} ;
155
174
}
156
175
157
- function updateTree ( items : TreeNode < T > [ ] , key : Key , update : ( node : TreeNode < T > ) => TreeNode < T > | null , originalMap : Map < Key , TreeNode < T > > ) {
158
- let node = originalMap . get ( key ) ;
159
- if ( ! node ) {
176
+ function updateTree ( items : TreeNode < T > [ ] , key : Key | null , update : ( node : TreeNode < T > ) => TreeNode < T > | null , originalMap : Map < Key , TreeNode < T > > ) {
177
+ let node = key == null ? null : originalMap . get ( key ) ;
178
+ if ( node == null ) {
160
179
return { items, nodeMap : originalMap } ;
161
180
}
162
181
let map = new Map < Key , TreeNode < T > > ( originalMap ) ;
@@ -233,7 +252,6 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
233
252
}
234
253
}
235
254
}
236
-
237
255
return {
238
256
items,
239
257
selectedKeys,
@@ -352,6 +370,8 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
352
370
353
371
// If parentKey is null, insert into the root.
354
372
if ( toParentKey == null ) {
373
+ // safe to reuse the original map since no node was actually removed, so we just need to update the one moved node
374
+ newMap = new Map ( originalMap ) ;
355
375
newMap . set ( movedNode . key , movedNode ) ;
356
376
return { items : [
357
377
...newItems . slice ( 0 , index ) ,
@@ -373,6 +393,39 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
373
393
} ) , newMap ) ;
374
394
} ) ;
375
395
} ,
396
+ moveBefore ( key : Key , keys : Iterable < Key > ) {
397
+ setItems ( ( prevState ) => {
398
+ let { items, nodeMap} = prevState ;
399
+ let node = nodeMap . get ( key ) ;
400
+ if ( ! node ) {
401
+ return prevState ;
402
+ }
403
+ let toParentKey = node . parentKey ?? null ;
404
+ let parent : null | TreeNode < T > = null ;
405
+ if ( toParentKey != null ) {
406
+ parent = nodeMap . get ( toParentKey ) ?? null ;
407
+ }
408
+ let toIndex = parent ?. children ? parent . children . indexOf ( node ) : items . indexOf ( node ) ;
409
+ return moveItems ( prevState , keys , parent , toIndex , updateTree ) ;
410
+ } ) ;
411
+ } ,
412
+ moveAfter ( key : Key , keys : Iterable < Key > ) {
413
+ setItems ( ( prevState ) => {
414
+ let { items, nodeMap} = prevState ;
415
+ let node = nodeMap . get ( key ) ;
416
+ if ( ! node ) {
417
+ return prevState ;
418
+ }
419
+ let toParentKey = node . parentKey ?? null ;
420
+ let parent : null | TreeNode < T > = null ;
421
+ if ( toParentKey != null ) {
422
+ parent = nodeMap . get ( toParentKey ) ?? null ;
423
+ }
424
+ let toIndex = parent ?. children ? parent . children . indexOf ( node ) : items . indexOf ( node ) ;
425
+ toIndex ++ ;
426
+ return moveItems ( prevState , keys , parent , toIndex , updateTree ) ;
427
+ } ) ;
428
+ } ,
376
429
update ( oldKey : Key , newValue : T ) {
377
430
setItems ( ( { items, nodeMap : originalMap } ) => updateTree ( items , oldKey , oldNode => {
378
431
let node : TreeNode < T > = {
@@ -389,3 +442,97 @@ export function useTreeData<T extends object>(options: TreeOptions<T>): TreeData
389
442
}
390
443
} ;
391
444
}
445
+
446
+ function moveItems < T extends object > (
447
+ state : TreeDataState < T > ,
448
+ keys : Iterable < Key > ,
449
+ toParent : TreeNode < T > | null ,
450
+ toIndex : number ,
451
+ updateTree : (
452
+ items : TreeNode < T > [ ] ,
453
+ key : Key ,
454
+ update : ( node : TreeNode < T > ) => TreeNode < T > | null ,
455
+ originalMap : Map < Key , TreeNode < T > >
456
+ ) => TreeDataState < T >
457
+ ) : TreeDataState < T > {
458
+ let { items, nodeMap} = state ;
459
+
460
+ let parent = toParent ;
461
+ let removeKeys = new Set ( keys ) ;
462
+ while ( parent ?. parentKey != null ) {
463
+ if ( removeKeys . has ( parent . key ) ) {
464
+ throw new Error ( 'Cannot move an item to be a child of itself.' ) ;
465
+ }
466
+ parent = nodeMap . get ( parent . parentKey ! ) ?? null ;
467
+ }
468
+
469
+ let originalToIndex = toIndex ;
470
+
471
+ let keyArray = Array . isArray ( keys ) ? keys : [ ...keys ] ;
472
+ // depth first search to put keys in order
473
+ let inOrderKeys : Map < Key , number > = new Map ( ) ;
474
+ let removedItems : Array < TreeNode < T > > = [ ] ;
475
+ let newItems = items ;
476
+ let newMap = nodeMap ;
477
+ let i = 0 ;
478
+
479
+ function traversal ( node , { inorder, postorder} ) {
480
+ inorder ?.( node ) ;
481
+ if ( node != null ) {
482
+ for ( let child of node . children ?? [ ] ) {
483
+ traversal ( child , { inorder, postorder} ) ;
484
+ postorder ?.( child ) ;
485
+ }
486
+ }
487
+ }
488
+
489
+ function inorder ( child ) {
490
+ // in-order so we add items as we encounter them in the tree, then we can insert them in expected order later
491
+ if ( keyArray . includes ( child . key ) ) {
492
+ inOrderKeys . set ( child . key , i ++ ) ;
493
+ }
494
+ }
495
+
496
+ function postorder ( child ) {
497
+ // remove items and update the tree from the leaves and work upwards toward the root, this way
498
+ // we don't copy child node references from parents inadvertently
499
+ if ( keyArray . includes ( child . key ) ) {
500
+ removedItems . push ( { ...newMap . get ( child . key ) ! , parentKey : toParent ?. key ?? null } ) ;
501
+ let { items : nextItems , nodeMap : nextMap } = updateTree ( newItems , child . key , ( ) => null , newMap ) ;
502
+ newItems = nextItems ;
503
+ newMap = nextMap ;
504
+ }
505
+ // decrement the index if the child being removed is in the target parent and before the target index
506
+ if ( child . parentKey === toParent ?. key
507
+ && keyArray . includes ( child . key )
508
+ && ( toParent ?. children ? toParent . children . indexOf ( child ) : items . indexOf ( child ) ) < originalToIndex ) {
509
+ toIndex -- ;
510
+ }
511
+ }
512
+
513
+ traversal ( { children : items } , { inorder, postorder} ) ;
514
+
515
+ let inOrderItems = removedItems . sort ( ( a , b ) => inOrderKeys . get ( a . key ) ! > inOrderKeys . get ( b . key ) ! ? 1 : - 1 ) ;
516
+ // If parentKey is null, insert into the root.
517
+ if ( ! toParent || toParent . key == null ) {
518
+ newMap = new Map ( nodeMap ) ;
519
+ inOrderItems . forEach ( movedNode => newMap . set ( movedNode . key , movedNode ) ) ;
520
+ return { items : [
521
+ ...newItems . slice ( 0 , toIndex ) ,
522
+ ...inOrderItems ,
523
+ ...newItems . slice ( toIndex )
524
+ ] , nodeMap : newMap } ;
525
+ }
526
+
527
+ // Otherwise, update the parent node and its ancestors.
528
+ return updateTree ( newItems , toParent . key , parentNode => ( {
529
+ key : parentNode . key ,
530
+ parentKey : parentNode . parentKey ,
531
+ value : parentNode . value ,
532
+ children : [
533
+ ...parentNode . children ! . slice ( 0 , toIndex ) ,
534
+ ...inOrderItems ,
535
+ ...parentNode . children ! . slice ( toIndex )
536
+ ]
537
+ } ) , newMap ) ;
538
+ }
0 commit comments