@@ -16,10 +16,14 @@ export const Canvas: React.FC<CanvasProps> = ({ className }) => {
1616 hoveredNodeId,
1717 setHoveredNodeId,
1818 draggingType,
19+ draggingNodeId,
20+ setDraggingNodeId,
1921 addNode,
22+ moveNode,
2023 } = useDesigner ( ) ;
2124
2225 const [ scale , setScale ] = useState ( 1 ) ;
26+ const canvasRef = React . useRef < HTMLDivElement > ( null ) ;
2327
2428 const handleClick = ( e : React . MouseEvent ) => {
2529 // Find closest element with data-obj-id
@@ -35,15 +39,18 @@ export const Canvas: React.FC<CanvasProps> = ({ className }) => {
3539 } ;
3640
3741 const handleDragOver = ( e : React . DragEvent ) => {
38- if ( ! draggingType ) return ;
42+ if ( ! draggingType && ! draggingNodeId ) return ;
3943 e . preventDefault ( ) ;
4044
4145 const target = ( e . target as Element ) . closest ( '[data-obj-id]' ) ;
4246 if ( target ) {
4347 e . stopPropagation ( ) ;
4448 const id = target . getAttribute ( 'data-obj-id' ) ;
45- setHoveredNodeId ( id ) ;
46- e . dataTransfer . dropEffect = 'copy' ;
49+ // Don't allow dropping on the node being dragged
50+ if ( id !== draggingNodeId ) {
51+ setHoveredNodeId ( id ) ;
52+ e . dataTransfer . dropEffect = draggingNodeId ? 'move' : 'copy' ;
53+ }
4754 } else {
4855 setHoveredNodeId ( null ) ;
4956 }
@@ -55,27 +62,85 @@ export const Canvas: React.FC<CanvasProps> = ({ className }) => {
5562
5663 const handleDrop = ( e : React . DragEvent ) => {
5764 e . preventDefault ( ) ;
58- if ( ! draggingType ) return ;
59-
65+
6066 const target = ( e . target as Element ) . closest ( '[data-obj-id]' ) ;
6167 const targetId = target ?. getAttribute ( 'data-obj-id' ) ;
6268
6369 if ( targetId ) {
6470 e . stopPropagation ( ) ;
65- const config = ComponentRegistry . getConfig ( draggingType ) ;
66- if ( config ) {
67- const newNode = {
68- type : draggingType ,
69- ...( config . defaultProps || { } ) ,
70- body : config . defaultChildren || undefined
71- } ;
72- addNode ( targetId , newNode ) ;
73- }
71+
72+ // Handle moving existing component
73+ if ( draggingNodeId ) {
74+ // Don't allow dropping on itself
75+ if ( draggingNodeId !== targetId ) {
76+ // TODO: Calculate proper insertion index based on drop position
77+ // For now, always insert at the beginning (index 0)
78+ moveNode ( draggingNodeId , targetId , 0 ) ;
79+ }
80+ setDraggingNodeId ( null ) ;
81+ }
82+ // Handle adding new component from palette
83+ else if ( draggingType ) {
84+ const config = ComponentRegistry . getConfig ( draggingType ) ;
85+ if ( config ) {
86+ const newNode = {
87+ type : draggingType ,
88+ ...( config . defaultProps || { } ) ,
89+ body : config . defaultChildren || undefined
90+ } ;
91+ addNode ( targetId , newNode ) ;
92+ }
93+ }
7494 }
7595
7696 setHoveredNodeId ( null ) ;
7797 } ;
7898
99+ // Make components in canvas draggable
100+ React . useEffect ( ( ) => {
101+ if ( ! canvasRef . current ) return ;
102+
103+ const handleDragStart = ( e : DragEvent ) => {
104+ const target = ( e . target as Element ) . closest ( '[data-obj-id]' ) ;
105+ if ( target && target . getAttribute ( 'data-obj-id' ) ) {
106+ const nodeId = target . getAttribute ( 'data-obj-id' ) ;
107+ // Don't allow dragging the root node
108+ if ( nodeId === schema . id ) {
109+ e . preventDefault ( ) ;
110+ return ;
111+ }
112+ setDraggingNodeId ( nodeId ) ;
113+ e . stopPropagation ( ) ;
114+ if ( e . dataTransfer ) {
115+ e . dataTransfer . effectAllowed = 'move' ;
116+ e . dataTransfer . setData ( 'text/plain' , nodeId || '' ) ;
117+ }
118+ }
119+ } ;
120+
121+ const handleDragEnd = ( ) => {
122+ setDraggingNodeId ( null ) ;
123+ } ;
124+
125+ // Add draggable attribute and event listeners to all elements with data-obj-id within canvas
126+ const elements = canvasRef . current . querySelectorAll ( '[data-obj-id]' ) ;
127+ elements . forEach ( el => {
128+ // Don't make root draggable
129+ if ( el . getAttribute ( 'data-obj-id' ) !== schema . id ) {
130+ el . setAttribute ( 'draggable' , 'true' ) ;
131+ el . addEventListener ( 'dragstart' , handleDragStart as EventListener ) ;
132+ el . addEventListener ( 'dragend' , handleDragEnd as EventListener ) ;
133+ }
134+ } ) ;
135+
136+ return ( ) => {
137+ elements . forEach ( el => {
138+ el . removeEventListener ( 'dragstart' , handleDragStart as EventListener ) ;
139+ el . removeEventListener ( 'dragend' , handleDragEnd as EventListener ) ;
140+ } ) ;
141+ } ;
142+ } , [ schema , setDraggingNodeId ] ) ;
143+
79144 // Inject styles for selection/hover using dynamic CSS
80145 // Using a more refined outline style
81146 const highlightStyles = `
@@ -84,6 +149,14 @@ export const Canvas: React.FC<CanvasProps> = ({ className }) => {
84149 transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
85150 }
86151
152+ [data-obj-id]:not([data-obj-id="${ schema . id } "]) {
153+ cursor: grab;
154+ }
155+
156+ [data-obj-id]:not([data-obj-id="${ schema . id } "]):active {
157+ cursor: grabbing;
158+ }
159+
87160 [data-obj-id="${ selectedNodeId } "] {
88161 outline: 2px solid #3b82f6 !important;
89162 outline-offset: -1px;
@@ -113,7 +186,11 @@ export const Canvas: React.FC<CanvasProps> = ({ className }) => {
113186 outline: 2px dashed #60a5fa !important;
114187 outline-offset: -2px;
115188 background-color: rgba(59, 130, 246, 0.05);
116- cursor: copy;
189+ cursor: ${ draggingNodeId ? 'move' : 'copy' } ;
190+ }
191+
192+ [data-obj-id="${ draggingNodeId } "] {
193+ opacity: 0.5;
117194 }
118195 ` ;
119196
@@ -135,6 +212,7 @@ export const Canvas: React.FC<CanvasProps> = ({ className }) => {
135212 </ div >
136213
137214 < div
215+ ref = { canvasRef }
138216 className = "flex-1 overflow-auto p-12 relative flex justify-center"
139217 onClick = { handleClick }
140218 onDragOver = { handleDragOver }
0 commit comments