1
- import { Direction , DropTarget , ItemDropTarget , Key , Node , RefObject } from '@react-types/shared' ;
2
- import { ListDropTargetDelegate } from './ListDropTargetDelegate' ;
3
-
4
- interface TreeDropTargetDelegateOptions {
5
- /**
6
- * The horizontal layout direction.
7
- * @default 'ltr'
8
- */
9
- direction ?: Direction
10
- }
1
+ import { Direction , DropTarget , DropTargetDelegate , ItemDropTarget , Key , Node } from '@react-types/shared' ;
11
2
12
3
interface TreeCollection < T > extends Iterable < Node < T > > {
13
4
getItem ( key : Key ) : Node < T > | null ,
@@ -36,11 +27,12 @@ interface PointerTracking {
36
27
} | null
37
28
}
38
29
39
- const X_SWITCH_THRESHOLD = 3 ;
40
- const Y_SWITCH_THRESHOLD = 2 ;
41
-
42
- export class TreeDropTargetDelegate < T > extends ListDropTargetDelegate {
43
- private state : TreeState < T > ;
30
+ const X_SWITCH_THRESHOLD = 5 ;
31
+ const Y_SWITCH_THRESHOLD = 5 ;
32
+ export class TreeDropTargetDelegate < T > {
33
+ private delegate : DropTargetDelegate | null = null ;
34
+ private state : TreeState < T > | null = null ;
35
+ private direction : Direction = 'ltr' ;
44
36
private pointerTracking : PointerTracking = {
45
37
lastY : 0 ,
46
38
lastX : 0 ,
@@ -49,26 +41,20 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
49
41
boundaryContext : null
50
42
} ;
51
43
52
- constructor ( state : TreeState < T > , ref : RefObject < HTMLElement | null > , options ?: TreeDropTargetDelegateOptions ) {
53
- super ( state . collection as Iterable < Node < unknown > > , ref , {
54
- direction : options ?. direction || 'ltr' ,
55
- orientation : 'vertical' ,
56
- layout : 'stack'
57
- } ) ;
44
+ setup ( delegate : DropTargetDelegate , state : TreeState < T > , direction : Direction ) : void {
45
+ this . delegate = delegate ;
58
46
this . state = state ;
47
+ this . direction = direction ;
59
48
}
60
49
61
- getDropTargetFromPoint ( x : number , y : number , isValidDropTarget : ( target : DropTarget ) => boolean ) : DropTarget {
62
- let baseTarget = super . getDropTargetFromPoint ( x , y , isValidDropTarget ) ;
50
+ getDropTargetFromPoint ( x : number , y : number , isValidDropTarget : ( target : DropTarget ) => boolean ) : DropTarget | null {
51
+ let baseTarget = this . delegate ! . getDropTargetFromPoint ( x , y , isValidDropTarget ) ;
63
52
64
53
if ( ! baseTarget || baseTarget . type === 'root' ) {
65
54
return baseTarget ;
66
55
}
67
56
68
- let target = this . resolveDropTarget ( baseTarget , x , y , isValidDropTarget ) ;
69
- console . log ( target ) ;
70
-
71
- return target ;
57
+ return this . resolveDropTarget ( baseTarget , x , y , isValidDropTarget ) ;
72
58
}
73
59
74
60
private resolveDropTarget (
@@ -82,21 +68,33 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
82
68
// Calculate movement directions
83
69
let deltaY = y - tracking . lastY ;
84
70
let deltaX = x - tracking . lastX ;
85
- let currentYMovement : 'up' | 'down' | null = null ;
86
- let currentXMovement : 'left' | 'right' | null = null ;
71
+ let currentYMovement : 'up' | 'down' | null = tracking . yDirection ;
72
+ let currentXMovement : 'left' | 'right' | null = tracking . xDirection ;
87
73
88
- if ( Math . abs ( deltaY ) > 2 ) {
74
+ if ( Math . abs ( deltaY ) > Y_SWITCH_THRESHOLD ) {
89
75
currentYMovement = deltaY > 0 ? 'down' : 'up' ;
90
76
tracking . yDirection = currentYMovement ;
91
77
tracking . lastY = y ;
92
78
}
93
79
94
- if ( Math . abs ( deltaX ) > 2 ) {
80
+ if ( Math . abs ( deltaX ) > X_SWITCH_THRESHOLD ) {
95
81
currentXMovement = deltaX > 0 ? 'right' : 'left' ;
96
82
tracking . xDirection = currentXMovement ;
97
83
tracking . lastX = x ;
98
84
}
99
85
86
+ // Normalize to 'after'
87
+ if ( target . dropPosition === 'before' ) {
88
+ let keyBefore = this . state ! . collection . getKeyBefore ( target . key ) ;
89
+ if ( keyBefore != null ) {
90
+ target = {
91
+ type : 'item' ,
92
+ key : keyBefore ,
93
+ dropPosition : 'after'
94
+ } as const ;
95
+ }
96
+ }
97
+
100
98
let potentialTargets = this . getPotentialTargets ( target , isValidDropTarget ) ;
101
99
102
100
if ( potentialTargets . length > 1 ) {
@@ -118,32 +116,20 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
118
116
}
119
117
120
118
let target = originalTarget ;
119
+ let collection = this . state ! . collection ;
121
120
122
- // Normalize to 'after'
123
- if ( originalTarget . dropPosition === 'before' ) {
124
- let keyBefore = this . state . collection . getKeyBefore ( originalTarget . key ) ;
125
- if ( keyBefore == null ) {
126
- return [ originalTarget ] ;
127
- }
128
- target = {
129
- type : 'item' ,
130
- key : keyBefore ,
131
- dropPosition : 'after'
132
- } as const ;
133
- }
134
-
135
- let currentItem = this . state . collection . getItem ( target . key ) ;
121
+ let currentItem = collection . getItem ( target . key ) ;
136
122
while ( currentItem && currentItem ?. type !== 'item' && currentItem . nextKey != null ) {
137
123
target . key = currentItem . nextKey ;
138
- currentItem = this . state . collection . getItem ( currentItem . nextKey ) ;
124
+ currentItem = collection . getItem ( currentItem . nextKey ) ;
139
125
}
140
126
141
127
let potentialTargets = [ target ] ;
142
128
143
129
// If target has children and is expanded, use "before first child"
144
- if ( currentItem && currentItem . hasChildNodes && this . state . expandedKeys . has ( currentItem . key ) && this . state . collection . getChildren ) {
130
+ if ( currentItem && currentItem . hasChildNodes && this . state ! . expandedKeys . has ( currentItem . key ) && collection . getChildren ) {
145
131
let firstChildItemNode : Node < any > | null = null ;
146
- for ( let child of this . state . collection . getChildren ( currentItem . key ) ) {
132
+ for ( let child of collection . getChildren ( currentItem . key ) ) {
147
133
if ( child . type === 'item' ) {
148
134
firstChildItemNode = child ;
149
135
break ;
@@ -172,8 +158,8 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
172
158
let ancestorTargets : ItemDropTarget [ ] = [ ] ;
173
159
174
160
while ( parentKey ) {
175
- let parentItem = this . state . collection . getItem ( parentKey ) ;
176
- let nextItem = parentItem ?. nextKey ? this . state . collection . getItem ( parentItem . nextKey ) : null ;
161
+ let parentItem = collection . getItem ( parentKey ) ;
162
+ let nextItem = parentItem ?. nextKey ? collection . getItem ( parentItem . nextKey ) : null ;
177
163
let isLastChildAtLevel = ! nextItem || nextItem . parentKey !== parentKey ;
178
164
179
165
if ( isLastChildAtLevel ) {
@@ -200,8 +186,8 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
200
186
201
187
// Handle converting "after" to "before next" for non-ambiguous cases
202
188
if ( potentialTargets . length === 1 ) {
203
- let nextKey = this . state . collection . getKeyAfter ( target . key ) ;
204
- let nextNode = nextKey ? this . state . collection . getItem ( nextKey ) : null ;
189
+ let nextKey = collection . getKeyAfter ( target . key ) ;
190
+ let nextNode = nextKey ? collection . getItem ( nextKey ) : null ;
205
191
if ( nextKey != null && nextNode && currentItem && nextNode . level != null && currentItem . level != null && nextNode . level > currentItem . level ) {
206
192
let beforeTarget = {
207
193
type : 'item' ,
@@ -230,67 +216,18 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
230
216
}
231
217
232
218
let tracking = this . pointerTracking ;
233
- let currentItem = this . state . collection . getItem ( originalTarget . key ) ;
219
+ let currentItem = this . state ! . collection . getItem ( originalTarget . key ) ;
234
220
let parentKey = currentItem ?. parentKey ;
235
221
236
222
if ( ! parentKey ) {
237
223
return potentialTargets [ 0 ] ;
238
224
}
239
-
240
- // Case 1: Exactly 2 potential targets - use Y movement only
241
- if ( potentialTargets . length === 2 ) {
242
- // Initialize boundary context if needed
243
- if ( ! tracking . boundaryContext || tracking . boundaryContext . parentKey !== parentKey ) {
244
- let initialTargetIndex = currentYMovement === 'up' ? 1 : 0 ;
245
-
246
- tracking . boundaryContext = {
247
- parentKey,
248
- lastChildKey : originalTarget . key ,
249
- preferredTargetIndex : initialTargetIndex ,
250
- lastSwitchY : y ,
251
- lastSwitchX : x ,
252
- entryDirection : tracking . yDirection
253
- } ;
254
- }
255
-
256
- let boundaryContext = tracking . boundaryContext ;
257
- let distanceFromLastYSwitch = Math . abs ( y - boundaryContext . lastSwitchY ) ;
258
-
259
- // Toggle between targets based on Y movement
260
- if ( distanceFromLastYSwitch > Y_SWITCH_THRESHOLD && currentYMovement ) {
261
- let currentIndex = boundaryContext . preferredTargetIndex || 0 ;
262
-
263
- if ( currentYMovement === 'down' && currentIndex === 0 ) {
264
- // Moving down from inner-most, switch to outer-most
265
- boundaryContext . preferredTargetIndex = 1 ;
266
- boundaryContext . lastSwitchY = y ;
267
- } else if ( currentYMovement === 'down' && currentIndex === 1 ) {
268
- // Moving down from outer-most, switch back to inner-most
269
- boundaryContext . preferredTargetIndex = 0 ;
270
- boundaryContext . lastSwitchY = y ;
271
- } else if ( currentYMovement === 'up' && currentIndex === 1 ) {
272
- // Moving up from outer-most, switch to inner-most
273
- boundaryContext . preferredTargetIndex = 0 ;
274
- boundaryContext . lastSwitchY = y ;
275
- } else if ( currentYMovement === 'up' && currentIndex === 0 ) {
276
- // Moving up from inner-most, switch to outer-most
277
- boundaryContext . preferredTargetIndex = 1 ;
278
- boundaryContext . lastSwitchY = y ;
279
- }
280
- }
281
-
282
- return potentialTargets [ boundaryContext . preferredTargetIndex || 0 ] ;
283
- }
284
225
285
- // Case 2: More than 2 potential targets - use Y for initial target, then X for switching levels
226
+ // More than 1 potential target - use Y for initial target, then X for switching levels
286
227
// Initialize boundary context if needed
287
228
if ( ! tracking . boundaryContext || tracking . boundaryContext . parentKey !== parentKey ) {
288
- let initialTargetIndex = 0 ; // Default to inner-most
289
- if ( tracking . yDirection === 'up' ) {
290
- // If entering from below, start with outer-most
291
- initialTargetIndex = potentialTargets . length - 1 ;
292
- }
293
-
229
+ // If entering from below, start with outer-most
230
+ let initialTargetIndex = tracking . yDirection === 'up' ? potentialTargets . length - 1 : 0 ;
294
231
tracking . boundaryContext = {
295
232
parentKey,
296
233
lastChildKey : originalTarget . key ,
@@ -303,6 +240,23 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
303
240
304
241
let boundaryContext = tracking . boundaryContext ;
305
242
let distanceFromLastXSwitch = Math . abs ( x - boundaryContext . lastSwitchX ) ;
243
+ let distanceFromLastYSwitch = Math . abs ( y - boundaryContext . lastSwitchY ) ;
244
+
245
+ // Toggle between targets based on Y movement
246
+ if ( distanceFromLastYSwitch > Y_SWITCH_THRESHOLD && currentYMovement ) {
247
+ let currentIndex = boundaryContext . preferredTargetIndex || 0 ;
248
+
249
+ if ( currentYMovement === 'down' && currentIndex === 0 ) {
250
+ // Moving down from inner-most, switch to outer-most
251
+ boundaryContext . preferredTargetIndex = potentialTargets . length - 1 ;
252
+ } else if ( currentYMovement === 'up' && currentIndex === potentialTargets . length - 1 ) {
253
+ // Moving up from outer-most, switch to inner-most
254
+ boundaryContext . preferredTargetIndex = 0 ;
255
+ }
256
+
257
+ // Reset x tracking so that moving diagonally doesn't cause flickering.
258
+ tracking . xDirection = null ;
259
+ }
306
260
307
261
// X movement controls level selection
308
262
if ( distanceFromLastXSwitch > X_SWITCH_THRESHOLD && currentXMovement ) {
@@ -337,6 +291,9 @@ export class TreeDropTargetDelegate<T> extends ListDropTargetDelegate {
337
291
}
338
292
}
339
293
}
294
+
295
+ // Reset y tracking so that moving diagonally doesn't cause flickering.
296
+ tracking . yDirection = null ;
340
297
}
341
298
342
299
let targetIndex = Math . max ( 0 , Math . min ( boundaryContext . preferredTargetIndex || 0 , potentialTargets . length - 1 ) ) ;
0 commit comments