Skip to content

Implement component reordering via drag-and-drop in designer canvas - #5

Merged
huangyiirene merged 6 commits into
mainfrom
copilot/add-draggable-components-in-designer
Jan 13, 2026
Merged

Implement component reordering via drag-and-drop in designer canvas#5
huangyiirene merged 6 commits into
mainfrom
copilot/add-draggable-components-in-designer

Conversation

CopilotAI commented Jan 13, 2026

Copy link
Copy Markdown
Contributor

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)

  • Added draggingNodeId state to track component being dragged
  • Implemented moveNode() to relocate nodes in schema tree with circular reference validation
  • Added helper functions: findNodeById(), moveNodeInTree()

Canvas (packages/designer/src/components/Canvas.tsx)

  • Made rendered components draggable via useEffect that attaches drag handlers to elements with data-obj-id
  • Enhanced drop handler to differentiate between palette drops (add) and canvas drops (move)
  • Added visual feedback: grab cursor, 50% opacity on drag, dashed outline on drop target
  • Root node remains non-draggable to prevent schema corruption

Tests (packages/designer/src/__tests__/drag-and-drop.test.tsx)

  • Added unit tests for draggingNodeId state management and moveNode() operations
  • All 6 tests passing

Usage

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:
Designer Initial

Selected card with visual feedback:
Card Selected

Limitations

  • Dropped components always insert at container start. Future enhancement: calculate insertion index from mouse position.
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.

CopilotAIand others added 4 commits January 13, 2026 14:03
…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>
CopilotAI changed the title [WIP] Add draggable components in the designerImplement component reordering via drag-and-drop in designer canvasJan 13, 2026
@huangyiirene
huangyiirene requested review from Copilot and removed request for huangyiireneJanuary 13, 2026 14:22

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
FileDescription
packages/designer/src/context/DesignerContext.tsxAdds draggingNodeId state, moveNode() function, and helper functions findNodeById() and moveNodeInTree() to support component reordering
packages/designer/src/components/Canvas.tsxImplements 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.tsxAdds unit tests for dragging state management and node movement operations
packages/designer/README.mdUpdates feature list and roadmap to reflect completed drag-and-drop functionality
packages/designer/IMPLEMENTATION.zh-CN.mdUpdates Chinese implementation documentation with completed features and known limitations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +142
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]);

CopilotAIJan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
e.stopPropagation();
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', nodeId || '');

CopilotAIJan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
e.dataTransfer.setData('text/plain',nodeId||'');
e.dataTransfer.setData('text/plain',nodeId);

Copilot uses AI. Check for mistakes.
@github-actions

Copy link
Copy Markdown
Contributor

✅ All checks passed!

  • ✅ Type check passed
  • ✅ Tests passed
  • ✅ Lint check completed

@huangyiirene
huangyiirene marked this pull request as ready for review January 13, 2026 14:30
@huangyiirene
huangyiirene merged commit adc057c into mainJan 13, 2026
5 checks passed
CopilotAI added a commit that referenced this pull request Jan 24, 2026
- 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>
CopilotAI added a commit that referenced this pull request Feb 20, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@huangyiirene@qq9340100