Skip to content

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

Merged
huangyiirene merged 6 commits intomainfrom
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 intomainfrom
copilot/add-draggable-components-in-designer

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 13, 2026

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.

Copilot AI and 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>
Copilot AI changed the title [WIP] Add draggable components in the designer Implement component reordering via drag-and-drop in designer canvas Jan 13, 2026
Copilot AI requested a review from huangyiirene January 13, 2026 14:14
@huangyiirene huangyiirene requested review from Copilot and removed request for huangyiirene January 13, 2026 14:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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
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.

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]);
Copy link

Copilot AI Jan 13, 2026

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 || '');
Copy link

Copilot AI Jan 13, 2026

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 main Jan 13, 2026
5 checks passed
Copilot AI 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>
Copilot AI 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 free to 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