The Loopar Framework’s drag-and-drop system allows you to visually construct user interfaces by dragging elements from the component palette and dropping them into droppable containers. The system provides real-time visual feedback and intelligent placement detection.
How It Works
The drag-and-drop system consists of three main components:
Draggable Elements
Components in the sidebar palette that can be dragged into the workspace
Droppable Containers
Areas where elements can be placed (forms, rows, columns, tabs, etc.)
Drag Context
State management that tracks the current drag operation and handles drop events
Creating New Elements
When you drag an element from the palette, the system creates a new instance:
// From design-element.jsx:13-23
const elementToCreate = () => {
const elementName = elementManage . elementName ( toElement );
return {
element: toElement ,
data: {
... elementName ,
key: elementName . key
}
}
}
Starting a Drag Operation
When you press down on a draggable element:
// From design-element.jsx:27-51
const dragStart = ( e ) => {
const el = elementToCreate ();
const rect = draggableRef . current . getBoundingClientRect ();
setInitializedDragging ( true );
setCurrentDragging ({
isNew: true ,
key: el . data . key ,
el ,
ref: draggableRef . current ,
initialPosition: {
x: e . clientX ,
y: e . clientY ,
},
offset: {
x: e . clientX - rect . x ,
y: e . clientY - rect . y ,
},
size: {
width: rect . width ,
height: rect . height ,
},
className: className + " mb-4" ,
});
};
The system captures the element’s size, position, and cursor offset to provide smooth visual feedback during dragging.
Droppable Areas
Any container element can act as a drop zone. The system automatically detects valid drop targets:
// From droppable.jsx:13-166
function DroppableContainer ({ data = {}, children , className , Component = "div" , ... props }) {
const [ elements , setElements ] = useState ( props . elements || []);
const [ position , setPosition ] = useState ( null );
const {
dropZone ,
currentDragging ,
movement ,
draggingEvent ,
dragging ,
setGlobalPosition ,
} = useDragAndDrop ();
const { droppableEvents , dragOver , __REFS__ } = useDroppable ();
const dropZoneRef = useRef ();
// ... drop handling logic
}
Visual Feedback
As you drag an element over a droppable container, the system provides visual cues:
// From droppable.jsx:142-148
const ClassNames = cn (
"rounded" ,
"bg-card h-full min-h-20 w-full p-2 space-y-3 py-3 pt-4" ,
dragOver ? 'bg-gradient-to-r from-slate-400/30 to-slate-600/60 shadow' : "" ,
className ,
renderizableProps . className
);
When a valid drop zone is detected, it highlights with a gradient background to indicate where the element will be placed.
Placement Detection
The system intelligently determines where to insert the dragged element based on cursor position:
// From droppable.jsx:71-95
const getIndex = useCallback (( currentKey ) => {
const brothers = getBrothers ( currentKey );
if ( ! movement ) return brothers . length ;
if ( brothers . length === 0 ) return 0 ;
const draggedTop = movement . y - currentDragging . offset . y ;
const draggedBottom = draggedTop + currentDragging . size . height ;
for ( let i = 0 ; i < brothers . length ; i ++ ) {
const rect = brothers [ i ];
if ( global . verticalDirection === UP ) {
if ( draggedTop < ( rect . y + ( rect . height * 0.75 ))) return i ;
} else {
if (( rect . y + ( rect . height * 0.25 )) > draggedBottom ) return i ;
}
}
return brothers . length ;
}, [
getBrothers ,
currentDragging ,
global . verticalDirection
]);
This algorithm:
Compares the dragged element’s position to existing sibling elements
Uses different thresholds based on drag direction (up vs down)
Determines the insertion index for smooth placement
Drop Preview
As you drag, a ghost preview shows where the element will be placed:
// From droppable.jsx:97-118
useEffect (() => {
if ( ! dragging || ! draggingEvent || ! movement ) return
if ( ! dropZone || dropZone !== data . key || position == null ) return ;
if ( ! currentDragging || currentDragging . key == data ?. key ) return ;
const rect = draggingEvent ;
const { size } = currentDragging ;
const { height } = size ;
if ( rect ) {
setElement (
< div
key = { currentDragging . key }
style = { { maxHeight: height } }
className = { ` ${ currentDragging . className } pointer-events-none opacity-60 border-2 border-dashed border-primary/70` }
dangerouslySetInnerHTML = { { __html: currentDragging . ref ?. innerHTML } }
/> ,
position
);
}
}, [ position , dropZone , data , currentDragging , dragging ]);
Completing the Drop
When you release the mouse, the system finalizes the placement:
// From DragAndDropContext.jsx:9-55
export function completeDrop ({ elements , targetKey , dropped , globalPosition }) {
const cloned = JSON . parse ( JSON . stringify ( elements ));
function insert ( nodes ) {
return nodes . map ( node => {
if ( ! node . data ) return node ;
if ( node . data . key === targetKey ) {
const filtered = ( node . elements || []). filter (
child => child . data . key !== dropped . el . data . key
);
filtered . splice ( globalPosition , 0 , dropped . el );
return { ... node , elements: filtered };
}
return {
... node ,
elements: insert ( node . elements || [])
};
});
}
function remove ( nodes ) {
return nodes . reduce (( acc , node ) => {
if ( ! node . data ) {
acc . push ( node );
return acc ;
}
if ( node . data . key === dropped . parentKey && dropped . parentKey !== targetKey ) {
const filtered = ( node . elements || []). filter (
child => child . data . key !== dropped . key
);
acc . push ({ ... node , elements: filtered });
} else {
acc . push ({
... node ,
elements: remove ( node . elements || [])
});
}
return acc ;
}, []);
}
return remove ( insert ( cloned ));
}
This function:
Clones the element tree to avoid mutations
Removes the element from its old location (if moving)
Inserts the element at the new position
Returns the updated tree structure
Drag and Drop Context
The DragAndDropProvider manages the global drag state:
// From DragAndDropContext.jsx:75-241
export const DragAndDropProvider = ( props ) => {
const { metaComponents , data } = props ;
const [ dropZone , setDropZone ] = useState ( null );
const [ currentDragging , setCurrentDragging ] = useState ( null );
const [ draggingEvent , setDraggingEvent ] = useState ( currentDragging ?. targetRect );
const [ movement , setMovement ] = useState ( null );
const [ dragging , setDragging ] = useState ( false );
const [ initializedDragging , setInitializedDragging ] = useState ( false );
const [ elements , setElements ] = useState ( metaComponents || []);
const [ globalPosition , setGlobalPosition ] = useState ( null );
const elementsRef = useRef ( elements );
const containerRef = useRef ( null );
// ... event handlers and state management
}
Available Context Values
import { useDragAndDrop } from "../droppable/DragAndDropContext" ;
const {
metaComponents , // Current element tree
dropZone , // Active drop zone key
setDropZone , // Function to set drop zone
currentDragging , // Element being dragged
setCurrentDragging , // Start drag operation
draggingEvent , // Current mouse position
movement , // Drag movement delta
handleDrop , // Complete drop operation
dragging , // Boolean: actively dragging
setInitializedDragging , // Initialize drag state
baseElements , // Original elements array
setGlobalPosition , // Set insertion position
containerRef // Reference to drag container
} = useDragAndDrop ();
The system automatically scrolls the page when dragging near edges:
// From DragAndDropContext.jsx:100-114
useEffect (() => {
if ( draggingEvent && movement && currentDragging ) {
const scrollSpeed = 10 ;
const scrollBuffer = 50 ;
const draggedTop = movement . y - currentDragging . offset . y ;
const draggedBottom = draggedTop + currentDragging . size . height ;
if ( global . verticalDirection === UP && draggedTop <= scrollBuffer ) {
window . scrollTo ( 0 , window . scrollY - scrollSpeed );
} else if ( global . verticalDirection === DOWN && draggedBottom > window . innerHeight - scrollBuffer ) {
window . scrollBy ( 0 , scrollSpeed );
}
}
}, [ draggingEvent , global . verticalDirection ]);
Moving Existing Elements
You can also drag existing elements to reorganize your layout:
// From base-designer.jsx:141-177
const updateElements = ( target , elements , current = null ) => {
const currentElements = metaComponents ;
const targetKey = target . data . key ;
const currentKey = current ? current . data . key : null ;
const lastParentKey = current ? current . parentKey : null ;
// Search target in structure and set elements in target
const setElementsInTarget = ( structure ) => {
return structure . map (( el ) => {
el . elements = el . data . key === targetKey ? elements
: setElementsInTarget ( el . elements || []);
return el ;
});
};
// Search target in structure and set elements in target, if target is self set directly in self
let newElements = targetKey === selfKey ? elements
: setElementsInTarget ( currentElements , selfKey );
// Search current in structure and delete current in last parent
const deleteCurrentOnLastParent = ( structure , parent ) => {
if ( lastParentKey === parent ) {
return structure . filter ( e => e . data . key !== currentKey );
}
return structure . map ( el => {
el . elements = deleteCurrentOnLastParent ( el . elements || [], el . data . key );
return el ;
});
};
if ( current && lastParentKey !== targetKey ) {
newElements = deleteCurrentOnLastParent ( newElements , selfKey );
}
setMeta ( JSON . stringify ( newElements ));
}
Always use the provided context methods to update element positions. Direct manipulation of the metadata can cause synchronization issues.
Best Practices
Organize your UI with proper layout elements (sections, rows, columns) before adding form fields or design elements.
Not all elements can contain children. Form fields like inputs and buttons cannot be drop targets.
Large nested structures can impact drag performance. Consider using tabs or fragments to organize complex layouts.
Use the drag toggle button to disable drag-and-drop when you need to select text or interact with form elements.
Next Steps
Element Editor Configure properties for dropped elements
Workspace Learn about the designer interface