Skip to content

list: Add drag and drop support to ListItem - #2553

Closed
lijingrs wants to merge 8 commits into
longbridge:mainfrom
lijingrs:list-item-drag-drop
Closed

list: Add drag and drop support to ListItem#2553
lijingrs wants to merge 8 commits into
longbridge:mainfrom
lijingrs:list-item-drag-drop

Conversation

@lijingrs

@lijingrs lijingrs commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Add on_drag, on_drop, and on_drag_hover methods to ListItem that delegate to the underlying base Stateful<Div>.

These methods enable drag-and-drop reordering when ListItem is used inside a Tree or any custom list layout. Without them, consumers cannot attach drag/drop handlers because the base field is private and ListItem does not implement Deref<Target=Div>.

Use case

In Verve (an API co-development platform built with gpui-component), the project tree needs drag-and-drop reordering for API requests and folders. The tree() component requires its render callback to return ListItem, so consumers have no way to wrap the item in an outer div with drag handlers. These three methods solve this by forwarding to the internal base div:

tree(&state, move |ix, entry, _selected, _window, cx| {
    ListItem::new(ix)
        .child(/* row content */)
        .on_drag(drag_value, move |drag, _pos, _window, cx| {
            // Mark as dragging + return a drag-preview entity
            cx.new(|_| drag.clone())
        })
        .on_drag_hover(move |hovering, _window, cx| {
            // Highlight as a potential drop target
        })
        .on_drop(move |drag, _window, cx| {
            // Reorder: move source relative to target
        })
})

The method signatures mirror GPUI's native Div::on_drag / on_drop / on_hover APIs, so they are immediately familiar.

What each method does

  • on_drag<T, W>(value, constructor) — registers a drag value of type T. When the user drags the row, constructor builds a drag-preview entity of type W: Render. Delegates to self.base.on_drag().
  • on_drop<T>(listener) — called when a drag value of type T is dropped onto this row. Delegates to self.base.on_drop().
  • on_drag_hover(listener) — called with true when the cursor enters the row during a drag, false when it leaves. Delegates to self.base.on_hover().

Break Changes

Fully additive. No existing API changes. Existing ListItem consumers continue to work unchanged.

How to Test

cargo run

The on_drag / on_drop / on_drag_hover methods can be tested in any story that uses ListItem inside a list. The methods delegate directly to the underlying Stateful<Div>, so they inherit all of GPUI's native drag-and-drop behavior.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo fmt --check.
  • [N/A] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) — not platform specific.

AI Assistance Disclosure

🤖 Parts of this PR were generated with AI assistance and subsequently reviewed by a human to match the project's existing code style and patterns. Specifically:

  • The three method signatures were modeled after the existing on_click / on_mouse_down / on_mouse_enter builder methods and GPUI's native Div::on_drag / on_drop / on_hover APIs.
  • The doc comments follow the same style as the existing methods in list_item.rs.

Add on_drag, on_drop, and on_drag_hover methods to ListItem that delegate
to the underlying base div. This enables drag-and-drop reordering when
ListItem is used inside a Tree or custom list layout.

The methods follow the same builder pattern as the existing on_click and
on_mouse_down methods.
@huacnlee

Copy link
Copy Markdown
Member

Please at least add simple example for this API in to story.

lijingrs and others added 7 commits July 13, 2026 09:34
- Add DragPreview entity for drag preview rendering
- Add with_drag_drop method to CompanyListItem demonstrating on_drag/on_drop/on_drag_hover usage
- Add draggable checkbox to enable/disable drag-and-drop demo
- Console output shows drag-over and drop events for easy testing

This addresses the PR review request for a simple example of the drag-and-drop API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fix type annotations for on_drag and on_drop closures
- Change method to use mutable self instead of chained calls
- Mark unused _cx parameter in render_item
- Separate clone variables for each drag handler to avoid conflicts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fix self.base.base chain that was causing syntax error
- Properly format on_drop call as single line with correct parameter types

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add explicit type annotations for all closure cx parameters: "&mut App"
- Fix self.base.base chain that was causing syntax error in on_drag_hover
- Ensure all drag-drop closures have proper type annotations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The on_drag closure must borrow the drag value (&Rc<Company>) to match
ListItem::on_drag's Fn(&T, ...) bound, fixing the E0631 type mismatch
that broke compilation of the story crate.

Also wire the "Draggable" checkbox to call toggle_draggable (matching
the selectable/searchable toggle pattern) so the method is no longer
dead code, and rename the unused window parameter to _window.

Co-Authored-By: Claude <noreply@anthropic.com>
@madcodelife

Copy link
Copy Markdown
Member

Thanks for the PR.

Your approach works, but it's not a complete API design. I tried implementing GPUI's InteractiveElement and StatefulInteractiveElement traits for ListItem instead, see #2639.

Closing this for now, we can discuss more if you have any questions. Thanks again.

@madcodelife madcodelife closed this Aug 3, 2026
madcodelife added a commit that referenced this pull request Aug 3, 2026
…nd drop (#2639)

## Description

Implement `InteractiveElement` and `StatefulInteractiveElement` for
`ListItem` by delegating to the inner `Stateful<Div>`, the same pattern
used by `Button`, `Checkbox`, `Radio`, and `Tab`.

- All GPUI native interaction APIs (`on_drag`, `on_drop`, `drag_over`,
`can_drop`, `on_hover`, `tooltip`, ...) become directly available on
`ListItem`, so rows can act as drag sources and drop targets inside
`Tree` or any custom list.
- Inherent `on_click` / `on_mouse_down` / `on_mouse_enter` keep their
precedence and disabled/separator gating, so existing consumers are
unaffected.
- Add a "Draggable" toggle to the List story that demonstrates real
reordering on drop.
- Document drag-and-drop usage in the List docs (en and zh-CN).

This supersedes #2553. Exposing the interactive traits gives the full
native API surface with unchanged semantics, instead of forwarding
`on_drag` / `on_drop` / `on_drag_hover` one method at a time
(`on_drag_hover` there is actually plain `on_hover`, which also fires
outside of drags).

## How to Test

- `cargo test -p gpui-component --lib list_item` — 3 passed
- `cargo clippy -- --deny warnings`
- `cargo run -- List`, check "Draggable", then drag a row onto another
row: a preview follows the cursor, an indicator line on the target row's
top or bottom edge shows the insertion position, and dropping reorders
the list. Click selection still works.
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.

3 participants