Skip to content

feat(spa): two-pane shuttle widget for filter_horizontal/vertical (#627) + 1.6.0#647

Merged
MartinCastroAlvarez merged 1 commit into
mainfrom
feat/shuttle-widget
May 31, 2026
Merged

feat(spa): two-pane shuttle widget for filter_horizontal/vertical (#627) + 1.6.0#647
MartinCastroAlvarez merged 1 commit into
mainfrom
feat/shuttle-widget

Conversation

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner

Closes #627 — the cross-repo shuttle-widget chain is now complete.

What

When the API (1.2.0+) emits widget: "shuttle_h" or "shuttle_v" on an M2M field (from ModelAdmin.filter_horizontal / filter_vertical), the SPA now renders Django's two-pane "available / chosen" shuttle widget instead of the single-list checkbox bank. Scales well past the ~50-option ceiling where the default falls over.

Behaviour

  • Two panes — Available (unselected) + Chosen (selected). Side-by-side for shuttle_h, stacked for shuttle_v. Mobile collapses both orientations to a single column.
  • Click to move — clicking an item moves it to the other pane. Keyboard parity: Enter or Space on a focused option triggers the same move (<li> carries role="option" + tabIndex=0).
  • Per-pane search — each pane has its own filter input that narrows the visible items client-side (no server roundtrip — the choices are already inlined in the wire).
  • "Choose all" / "Remove all" — act on the FILTERED view, so typing a search term + clicking the link affects only those items.
  • Order preservation — moving items INTO Chosen appends; moving items OUT keeps the relative order of survivors. Matches Django.
  • Numeric pk round-trip — numeric choice values are emitted as numbers (not stringified) so the API receives the right type.

Cross-repo chain

Repo Released
django-admin-rest-api 1.2.0 PyPI — emits the new hints
django-admin-react 1.6.0 this PR — renders the widget

SPA's django-admin-rest-api constraint tightens ^1.1.0^1.2.0 so the new hint is guaranteed available; consumers on 1.1.x with this SPA build would see the silent-noop pattern return.

Tests

Eight new vitests in ShuttleSelect.test.tsx cover two-pane render, click-to-move both directions, per-pane search, Choose all / Remove all on the filtered view, orientation grid-layout toggle, and numeric pk round-trip.

Verification

  • poetry run pytest -q61 / 61 ✓ on Django 4.2.30
  • pnpm test202 / 202 ✓ (up from 194; +8 new)
  • pnpm -r typecheck
  • pnpm lint
  • pnpm -w build

Minor bump rationale

1.5.11.6.0. New user-visible widget per SemVer's "additive features" guideline. Matches the symmetric 1.2.0 minor bump on django-admin-rest-api.

🤖 Generated with Claude Code

…) + 1.6.0

Closes #627 — the cross-repo shuttle-widget chain is now complete.

## What

When the API (1.2.0+) emits ``widget: "shuttle_h"`` or ``"shuttle_v"``
on an M2M field (from ``ModelAdmin.filter_horizontal`` /
``filter_vertical``), the SPA now renders Django's two-pane
"available / chosen" shuttle widget instead of the single-list
checkbox bank. Scales well past the ~50-option ceiling where the
default falls over.

## Behaviour

- **Two panes** — Available (unselected) + Chosen (selected). Side-by-
  side for ``shuttle_h``, stacked for ``shuttle_v``. Mobile collapses
  both orientations to a single column.
- **Click to move** — clicking an item moves it to the other pane.
  Keyboard parity: Enter or Space on a focused option triggers the
  same move (the ``<li>`` carries ``role="option"`` + ``tabIndex=0``).
- **Per-pane search** — each pane has its own filter input that
  narrows the visible items client-side (no server roundtrip — the
  choices are already inlined in the wire payload).
- **"Choose all" / "Remove all"** — act on the FILTERED view, so
  typing a search term + clicking the link affects only those items.
- **Order preservation** — moving items INTO Chosen appends; moving
  items OUT keeps the relative order of survivors. Matches Django.
- **Numeric pk round-trip** — numeric choice values are emitted as
  numbers (not stringified) so the API receives the right type.

## Cross-repo chain

| Repo | Released |
|---|---|
| ``django-admin-rest-api`` 1.2.0 | [PyPI](https://pypi.org/project/django-admin-rest-api/1.2.0/) — emits the new hints |
| ``django-admin-react`` 1.6.0 | **this PR** — renders the widget |

SPA's ``django-admin-rest-api`` constraint tightens ``^1.1.0`` →
``^1.2.0`` so the new hint is guaranteed available; consumers on
1.1.x with this SPA build would see the silent-noop pattern
return (which is what landed #626 / #627 in the first place).

## Tests

Eight new vitests in ``ShuttleSelect.test.tsx``:
- Two-pane render (Available + Chosen).
- Click-to-move both directions.
- Per-pane search filter (case-insensitive).
- Choose all / Remove all bulk actions over the filtered view.
- Horizontal vs vertical orientation toggles the grid layout.
- Numeric pk round-trip.

## Verification

- ``poetry run pytest -q`` — **61 / 61 ✓** on Django 4.2.30
- ``pnpm test`` — **202 / 202 ✓** (up from 194; +8 new)
- ``pnpm -r typecheck`` ✓
- ``pnpm lint`` ✓
- ``pnpm -w build`` ✓

## Minor bump rationale

``1.5.1`` → ``1.6.0``. New user-visible widget per SemVer's
"additive features" guideline. Matches the symmetric ``1.2.0`` minor
bump on ``django-admin-rest-api``.

Closes #627.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MartinCastroAlvarez MartinCastroAlvarez merged commit f8f6fda into main May 31, 2026
6 checks passed
@MartinCastroAlvarez MartinCastroAlvarez deleted the feat/shuttle-widget branch May 31, 2026 12:28
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.

[audit] filter_horizontal / filter_vertical dual-list M2M widget missing — falls back to a generic multi-select

2 participants