Implement component reordering via drag-and-drop in designer canvas#5
Conversation
…mponents Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
…DO comment Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR implements drag-and-drop functionality for reordering components within the designer canvas. Previously, components could only be added from the palette but not rearranged after placement.
Changes:
- Added state management for tracking dragged nodes and a
moveNode()function with circular reference validation - Enabled draggable behavior for canvas components with visual feedback (grab cursor, opacity change, dashed outline)
- Created comprehensive unit tests for the new drag-and-drop operations
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/designer/src/context/DesignerContext.tsx | Adds draggingNodeId state, moveNode() function, and helper functions findNodeById() and moveNodeInTree() to support component reordering |
| packages/designer/src/components/Canvas.tsx | Implements draggable behavior for canvas components, drop handling for both palette and canvas drags, and visual feedback styles |
| packages/designer/src/tests/drag-and-drop.test.tsx | Adds unit tests for dragging state management and node movement operations |
| packages/designer/README.md | Updates feature list and roadmap to reflect completed drag-and-drop functionality |
| packages/designer/IMPLEMENTATION.zh-CN.md | Updates Chinese implementation documentation with completed features and known limitations |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| React.useEffect(() => { | ||
| if (!canvasRef.current) return; | ||
|
|
||
| const handleDragStart = (e: DragEvent) => { | ||
| const target = (e.target as Element).closest('[data-obj-id]'); | ||
| if (target && target.getAttribute('data-obj-id')) { | ||
| const nodeId = target.getAttribute('data-obj-id'); | ||
| // Don't allow dragging the root node | ||
| if (nodeId === schema.id) { | ||
| e.preventDefault(); | ||
| return; | ||
| } | ||
| setDraggingNodeId(nodeId); | ||
| e.stopPropagation(); | ||
| if (e.dataTransfer) { | ||
| e.dataTransfer.effectAllowed = 'move'; | ||
| e.dataTransfer.setData('text/plain', nodeId || ''); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const handleDragEnd = () => { | ||
| setDraggingNodeId(null); | ||
| }; | ||
|
|
||
| // Add draggable attribute and event listeners to all elements with data-obj-id within canvas | ||
| const elements = canvasRef.current.querySelectorAll('[data-obj-id]'); | ||
| elements.forEach(el => { | ||
| // Don't make root draggable | ||
| if (el.getAttribute('data-obj-id') !== schema.id) { | ||
| el.setAttribute('draggable', 'true'); | ||
| el.addEventListener('dragstart', handleDragStart as EventListener); | ||
| el.addEventListener('dragend', handleDragEnd as EventListener); | ||
| } | ||
| }); | ||
|
|
||
| return () => { | ||
| elements.forEach(el => { | ||
| el.removeEventListener('dragstart', handleDragStart as EventListener); | ||
| el.removeEventListener('dragend', handleDragEnd as EventListener); | ||
| }); | ||
| }; | ||
| }, [schema, setDraggingNodeId]); |
There was a problem hiding this comment.
The useEffect hook re-runs whenever the schema changes, which could be frequent during editing. This causes all drag event listeners to be removed and re-attached on every schema update, which is inefficient. Consider using a MutationObserver to dynamically handle newly added elements, or add schema.id to the dependency array instead of the entire schema object to reduce unnecessary re-renders.
| e.stopPropagation(); | ||
| if (e.dataTransfer) { | ||
| e.dataTransfer.effectAllowed = 'move'; | ||
| e.dataTransfer.setData('text/plain', nodeId || ''); |
There was a problem hiding this comment.
The fallback to empty string when nodeId is null/undefined is unnecessary since nodeId has already been null-checked at line 108. The || '' can be removed for cleaner code.
| e.dataTransfer.setData('text/plain', nodeId || ''); | |
| e.dataTransfer.setData('text/plain', nodeId); |
|
✅ All checks passed!
|
- Fix handleExportCSV to guard on gridRef.current?.api (issue #1) - Add dedicated onContextMenuAction callback instead of overloading onCellClicked (issue #2) - Remove icon property from customItems to prevent HTML injection (issue #3) - Remove validation claim from README - only basic AG Grid editing (issue #4) - Add test assertions for all new inputs (editable, exportConfig, etc.) (issue #5) - Fix onExport type to only support 'csv' format (issue #6) - Remove unused ColumnConfig properties (autoSize, groupable) (issue #9) - Type schema props with proper interfaces instead of 'any' (issue #10) - Update export description to only mention CSV (issue #11) - Add AG Grid Community vs Enterprise section to docs (issue #8) - Update README and docs with new callback and clarifications All tests pass (8/8), lint clean (0 errors) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…warning, i18n fallback - Issue #1: Normalize `in`/`not in` operators to backend-compatible `or`/`and` of `=`/`!=` - Issue #2: Filter merging now validates and filters empty conditions - Issue #3: CSV export safely serializes arrays (semicolon-separated) and objects (JSON) - Issue #5: Request counter prevents stale data from overwriting latest results - Issue #6: PullToRefresh resets pull distance immediately to prevent UI lock - Issue #7: $top configurable via schema.pagination, data limit warning shown - Issue #8: Extended i18n fallback translations for all ListView labels - Issue #9: Defensive null checks in effectiveFields for mismatched objectDef - Issue #10: Added FilterNormalization, Export, and DataFetch test suites Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Implements drag-and-drop for reordering components within the designer canvas. Previously, components could only be added from the palette; they could not be rearranged.
Changes
DesignerContext (
packages/designer/src/context/DesignerContext.tsx)draggingNodeIdstate to track component being draggedmoveNode()to relocate nodes in schema tree with circular reference validationfindNodeById(),moveNodeInTree()Canvas (
packages/designer/src/components/Canvas.tsx)useEffectthat attaches drag handlers to elements withdata-obj-idTests (
packages/designer/src/__tests__/drag-and-drop.test.tsx)draggingNodeIdstate management andmoveNode()operationsUsage
Components in the canvas now show a grab cursor and can be dragged into other containers. Drop position is currently fixed at index 0 (beginning of container).
Screenshots
Initial state with two cards:

Selected card with visual feedback:

Limitations
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.