Skip to content

Add make_sortable() for drag-and-drop sorting on container elements#5855

Open
falkoschindler wants to merge 10 commits intomainfrom
make-sortable
Open

Add make_sortable() for drag-and-drop sorting on container elements#5855
falkoschindler wants to merge 10 commits intomainfrom
make-sortable

Conversation

@falkoschindler
Copy link
Copy Markdown
Contributor

@falkoschindler falkoschindler commented Mar 9, 2026

Motivation

Feature request: #932
Supersedes PRs #4656 and #5464

Users have long requested built-in drag-and-drop sorting support. PR #4656 introduced SortableJS as a dedicated ui.sortable element, and #5464 was another experiment along the same lines. After extensive discussion in #932, we concluded that "sortable" is a feature, not an element — a ui.column that can be reordered is still a column.

This PR implements that conclusion: a .make_sortable() method on container elements (ui.column, ui.row, ui.card, ui.grid, ui.list, ui.expansion, ui.scroll_area). It wraps SortableJS and keeps the server-side element tree in sync automatically. The container keeps its original type — a ui.column stays a ui.column.

Implementation

  • SortableElement mixin adds make_sortable() to container elements, returning a Sortable controller with enable(), disable(), destroy(), and read/write properties (animation, handle, group, ghost_class). An options property (backed by ObservableDict) auto-pushes changes to the client, following the same pattern as AG Grid.
  • Sortable controller initializes SortableJS on the client, syncs the client-side element tree on drop (preventing snap-back), and fires a sortend event with element references (item, source, target) and indices. is a hidden Vue component (Element subclass) that manages SortableJS via a .js component file. Options are synced reactively through props. The JS-side onEnd callback syncs the client-side element tree (preventing snap-back) and dispatches a sortend CustomEvent on the container element with element references (item, source, target) and indices.
  • SortableEventArguments dataclass provides typed event args.
  • SortableJS is bundled as an ESM module (same rollup pattern as ag-grid).
  • Auto-cleanup: destroying the container element automatically destroys the sortable. Vue's unmounted() lifecycle hook automatically destroys the SortableJS instance — no manual cleanup needed.
  • Duplicate protection: calling make_sortable() twice raises RuntimeError.

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • This is not a security issue.
  • Pytests have been added (or are not necessary).
  • Documentation has been added (or is not necessary).

@falkoschindler falkoschindler added this to the 3.10 milestone Mar 9, 2026
@falkoschindler falkoschindler added feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it labels Mar 9, 2026
falkoschindler and others added 2 commits April 3, 2026 17:50
Use cursor-grab/cursor-grabbing instead of cursor-pointer in demos,
document custom HTML ID limitation and options override behavior,
add cross-container on_end note, and sync test comment with JS source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@falkoschindler
Copy link
Copy Markdown
Contributor Author

There are still two review comments from Claude I need to look into. Apart from that the PR is complete. Should be good for early 3.11.


  1. Sortable element placement and lifecycle

    Sortable(self, ...) in make_sortable() creates the element in whatever build context is current at call time — typically the page root, not as a child of the container being made sortable.

    If the container is programmatically deleted (e.g. container.delete()), the Sortable element is not automatically cleaned up because it isn't a descendant. The SortableJS instance would linger with a stale DOM reference until the page is closed.

    Suggestion: Either create the Sortable as a child of the container (using with self:) or register an on_delete callback on the container to destroy the Sortable.

    Note: Option A would make the Sortable a visible child in the slot, which would be affected by sorting. So Option B (on_delete callback) or using a non-default slot might be better. Worth thinking through.

  2. Missing test coverage

    • enable() / disable(): No test verifies these methods work
    • Runtime property changes: No test for changing animation, handle, group, or ghost_class after initialization
    • Duplicate make_sortable() call: No test verifying the RuntimeError is raised

@falkoschindler falkoschindler modified the milestones: 3.10, 3.11 Apr 3, 2026
@falkoschindler falkoschindler added the 🟠 major Priority: Important, but not urgent label Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it 🟠 major Priority: Important, but not urgent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant