@@ -23,6 +23,7 @@ type Props<T> = {
2323 getChildren : ( value : T ) => T [ ]
2424 getIcon ?: ( value : T ) => JSX . Element
2525 getLabel : ( value : T ) => string | JSX . Element
26+ getSelectedItems ?: ( ) => T [ ]
2627 isOpen : ( value : T ) => boolean
2728 isSelected : ( value : T ) => boolean
2829 isHidden : ( value : T ) => boolean
@@ -64,6 +65,7 @@ export function Tree<T>() {
6465 getId,
6566 getChildren,
6667 getLabel,
68+ getSelectedItems,
6769 isOpen,
6870 isSelected,
6971 onSelect,
@@ -111,33 +113,68 @@ export function Tree<T>() {
111113 [ getId , getChildren ]
112114 )
113115
114- const [ , drag ] = useDrag (
115- ( ) => ( {
116- type : dndType ,
117- canDrag : enableDrag ,
118- item : { value, context : getDragContext ( ) }
119- } ) ,
120- [ value ]
116+ const canDropMultiple = useCallback (
117+ ( target : T , sources : T [ ] ) : boolean => {
118+ // Check if any source is the target itself
119+ if ( sources . some ( ( source ) => getId ( target ) === getId ( source ) ) ) return false
120+ // Check if any source is a descendant of the target
121+ if ( sources . some ( ( source ) => isDescendantOf ( target , source ) ) ) return false
122+ // Recursively check children
123+ return getChildren ( target ) . every ( ( $ ) => canDropMultiple ( $ , sources ) )
124+ } ,
125+ [ getId , getChildren ]
126+ )
127+
128+ const isDescendantOf = useCallback (
129+ ( ancestor : T , descendant : T ) : boolean => {
130+ const children = getChildren ( ancestor )
131+ if ( children . some ( ( child ) => getId ( child ) === getId ( descendant ) ) ) return true
132+ return children . some ( ( child ) => isDescendantOf ( child , descendant ) )
133+ } ,
134+ [ getId , getChildren ]
121135 )
122136
123137 const [ { isHover } , drop ] = useDrop (
124138 ( ) => ( {
125139 accept : dndType ,
126- drop : ( { value : item } : { value : T } , monitor ) => {
140+ drop : ( item : { items : T [ ] ; context : unknown } , monitor ) => {
127141 const dropTypeValue = dropType || dropTypeRef . current
128- if ( monitor . didDrop ( ) || ! canDrop ( item , value ) || ! dropTypeValue ) return
129- onDrop ( item , value , dropTypeValue )
142+ if ( monitor . didDrop ( ) || ! dropTypeValue ) return
143+
144+ const { items } = item
145+ const isMultipleDrag = items . length > 1
146+
147+ if ( isMultipleDrag ) {
148+ if ( ! canDropMultiple ( value , items ) ) return
149+ items . forEach ( ( sourceItem ) => onDrop ( sourceItem , value , dropTypeValue ) )
150+ } else {
151+ const sourceItem = items [ 0 ]
152+ if ( ! canDrop ( sourceItem , value ) ) return
153+ onDrop ( sourceItem , value , dropTypeValue )
154+ }
130155 } ,
131- hover : ( { value : item } , monitor ) => {
132- if ( ! ref . current || item === value ) {
156+ hover : ( item : { items : T [ ] ; context : unknown } , monitor ) => {
157+ if ( ! ref . current ) {
158+ dropTypeRef . current = ''
159+ return setDropType ( '' )
160+ }
161+
162+ const { items } = item
163+
164+ // check if hovering over one of the dragged items
165+ if ( items . some ( ( sourceItem ) => getId ( sourceItem ) === getId ( value ) ) ) {
133166 dropTypeRef . current = ''
134167 return setDropType ( '' )
135168 }
136169
137170 const coords = monitor . getClientOffset ( ) as XYCoord
138171 const rect = ref . current . getBoundingClientRect ( )
139172 const dropType = calculateDropType ( coords . y , rect )
140- const enableReorder = canReorder ? canReorder ( item , value , dropType ) : true
173+
174+ const enableReorder = canReorder
175+ ? items . every ( ( sourceItem ) => canReorder ( sourceItem , value , dropType ) )
176+ : true
177+
141178 const newDropTypeValue = enableReorder ? dropType : ''
142179
143180 setDropType ( newDropTypeValue )
@@ -147,15 +184,16 @@ export function Tree<T>() {
147184 isHover : monitor . isOver ( { shallow : true } )
148185 } )
149186 } ) ,
150- [ value , dropType , onDrop , canDrop ]
187+ [ value , dropType , onDrop , canDrop , canDropMultiple , canReorder , getId ]
151188 )
152189
153190 const quitEditMode = ( ) => setEditMode ( false )
154191 const quitInsertMode = ( ) => setInsertMode ( false )
155192
156193 const handleSelect = ( event : React . MouseEvent ) => {
157194 const isMac = / M a c | i P h o n e | i P o d | i P a d / . test ( navigator . userAgent )
158- const isCtrlClick = ( isMac ? event . type === ClickType . CONTEXT_MENU : event . type === ClickType . CLICK ) && event . ctrlKey
195+ const isCtrlClick =
196+ ( isMac ? event . type === ClickType . CONTEXT_MENU : event . type === ClickType . CLICK ) && event . ctrlKey
159197 const isShiftClick = event . type === ClickType . CLICK && event . shiftKey
160198 const isDoubleClick = event . type === ClickType . CLICK && event . detail > 1 && onDoubleSelect
161199 const clickType = isCtrlClick ? 'ctrl' : isShiftClick ? 'shift' : 'single'
@@ -193,6 +231,29 @@ export function Tree<T>() {
193231 }
194232
195233 const sdk = useSdk ( )
234+
235+ const [ , drag ] = useDrag (
236+ ( ) => ( {
237+ type : dndType ,
238+ canDrag : enableDrag ,
239+ item : ( ) => {
240+ const selectedItems = getSelectedItems ? getSelectedItems ( ) : [ ]
241+ // if this item is selected and there are multiple selections, drag all selected items
242+ if ( selectedItems . length > 1 && selectedItems . some ( ( item ) => getId ( item ) === getId ( value ) ) ) {
243+ return {
244+ items : selectedItems ,
245+ context : getDragContext ( )
246+ }
247+ }
248+ return {
249+ items : [ value ] ,
250+ context : getDragContext ( )
251+ }
252+ } ,
253+ } ) ,
254+ [ value , getSelectedItems , getId ]
255+ )
256+
196257 const handleRemove = ( ) => {
197258 if ( isEntity && sdk ) {
198259 const selectedEntities = sdk . operations . getSelectedEntities ( )
@@ -269,7 +330,9 @@ export function Tree<T>() {
269330 ) }
270331 </ div >
271332 < TreeChildren { ...props } />
272- { insertMode && < Input value = "" onCancel = { quitInsertMode } onSubmit = { handleAddChild } onBlur = { quitInsertMode } /> }
333+ { insertMode && (
334+ < Input value = "" onCancel = { quitInsertMode } onSubmit = { handleAddChild } onBlur = { quitInsertMode } />
335+ ) }
273336 </ div >
274337 )
275338 } )
0 commit comments