@@ -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,65 @@ 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+ if ( sources . some ( ( source ) => getId ( target ) === getId ( source ) ) ) return false
119+ if ( sources . some ( ( source ) => isDescendantOf ( target , source ) ) ) return false
120+ return getChildren ( target ) . every ( ( $ ) => canDropMultiple ( $ , sources ) )
121+ } ,
122+ [ getId , getChildren ]
123+ )
124+
125+ const isDescendantOf = useCallback (
126+ ( ancestor : T , descendant : T ) : boolean => {
127+ const children = getChildren ( ancestor )
128+ if ( children . some ( ( child ) => getId ( child ) === getId ( descendant ) ) ) return true
129+ return children . some ( ( child ) => isDescendantOf ( child , descendant ) )
130+ } ,
131+ [ getId , getChildren ]
121132 )
122133
123134 const [ { isHover } , drop ] = useDrop (
124135 ( ) => ( {
125136 accept : dndType ,
126- drop : ( { value : item } : { value : T } , monitor ) => {
137+ drop : ( item : { items : T [ ] ; context : unknown } , monitor ) => {
127138 const dropTypeValue = dropType || dropTypeRef . current
128- if ( monitor . didDrop ( ) || ! canDrop ( item , value ) || ! dropTypeValue ) return
129- onDrop ( item , value , dropTypeValue )
139+ if ( monitor . didDrop ( ) || ! dropTypeValue ) return
140+
141+ const { items } = item
142+ const isMultipleDrag = items . length > 1
143+
144+ if ( isMultipleDrag ) {
145+ if ( ! canDropMultiple ( value , items ) ) return
146+ items . forEach ( ( sourceItem ) => onDrop ( sourceItem , value , dropTypeValue ) )
147+ } else {
148+ const sourceItem = items [ 0 ]
149+ if ( ! canDrop ( sourceItem , value ) ) return
150+ onDrop ( sourceItem , value , dropTypeValue )
151+ }
130152 } ,
131- hover : ( { value : item } , monitor ) => {
132- if ( ! ref . current || item === value ) {
153+ hover : ( item : { items : T [ ] ; context : unknown } , monitor ) => {
154+ if ( ! ref . current ) {
155+ dropTypeRef . current = ''
156+ return setDropType ( '' )
157+ }
158+
159+ const { items } = item
160+
161+ // check if hovering over one of the dragged items
162+ if ( items . some ( ( sourceItem ) => getId ( sourceItem ) === getId ( value ) ) ) {
133163 dropTypeRef . current = ''
134164 return setDropType ( '' )
135165 }
136166
137167 const coords = monitor . getClientOffset ( ) as XYCoord
138168 const rect = ref . current . getBoundingClientRect ( )
139169 const dropType = calculateDropType ( coords . y , rect )
140- const enableReorder = canReorder ? canReorder ( item , value , dropType ) : true
170+
171+ const enableReorder = canReorder
172+ ? items . every ( ( sourceItem ) => canReorder ( sourceItem , value , dropType ) )
173+ : true
174+
141175 const newDropTypeValue = enableReorder ? dropType : ''
142176
143177 setDropType ( newDropTypeValue )
@@ -147,15 +181,16 @@ export function Tree<T>() {
147181 isHover : monitor . isOver ( { shallow : true } )
148182 } )
149183 } ) ,
150- [ value , dropType , onDrop , canDrop ]
184+ [ value , dropType , onDrop , canDrop , canDropMultiple , canReorder , getId ]
151185 )
152186
153187 const quitEditMode = ( ) => setEditMode ( false )
154188 const quitInsertMode = ( ) => setInsertMode ( false )
155189
156190 const handleSelect = ( event : React . MouseEvent ) => {
157191 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
192+ const isCtrlClick =
193+ ( isMac ? event . type === ClickType . CONTEXT_MENU : event . type === ClickType . CLICK ) && event . ctrlKey
159194 const isShiftClick = event . type === ClickType . CLICK && event . shiftKey
160195 const isDoubleClick = event . type === ClickType . CLICK && event . detail > 1 && onDoubleSelect
161196 const clickType = isCtrlClick ? 'ctrl' : isShiftClick ? 'shift' : 'single'
@@ -193,6 +228,29 @@ export function Tree<T>() {
193228 }
194229
195230 const sdk = useSdk ( )
231+
232+ const [ , drag ] = useDrag (
233+ ( ) => ( {
234+ type : dndType ,
235+ canDrag : enableDrag ,
236+ item : ( ) => {
237+ const selectedItems = getSelectedItems ? getSelectedItems ( ) : [ ]
238+ // if this item is selected and there are multiple selections, drag all selected items
239+ if ( selectedItems . length > 1 && selectedItems . some ( ( item ) => getId ( item ) === getId ( value ) ) ) {
240+ return {
241+ items : selectedItems ,
242+ context : getDragContext ( )
243+ }
244+ }
245+ return {
246+ items : [ value ] ,
247+ context : getDragContext ( )
248+ }
249+ }
250+ } ) ,
251+ [ value , getSelectedItems , getId ]
252+ )
253+
196254 const handleRemove = ( ) => {
197255 if ( isEntity && sdk ) {
198256 const selectedEntities = sdk . operations . getSelectedEntities ( )
0 commit comments