dioxus-dnd 2.2.0

Modular, accessible drag-and-drop for Dioxus: sortable lists, kanban boards, trees, grids, file drops, multi-select, touch support and more
Documentation
# Changelog

## 2.2.0 - 2026-07-08

Cross-type drops, documented and de-trapped. Providers stay monomorphic -
that's the crate's core guarantee - but two things now make living next to
a second provider first-class.

### Added

- `ZoneRegistry::contains(id)` and `ZoneRegistry::ascend(current)`. The
  `ParentZone` context is shared across payload types, so a `DropZone<A>`
  nested inside a `DropZone<B>` records B's id as its parent - an id that
  only resolves in *B's* registry. `ascend` returns a zone's parent only
  when it's registered in the same registry.
- `ParentZone` is exported from the prelude, completing the public kit
  (`use_zone_registry`, `use_zone_id`, `ZoneRecord`) for building custom
  zones - including bridge zones registered in two type-worlds at once.
- README section **Mixing payload types**: when one provider with an enum
  payload is the answer, and when to bridge two providers by registering
  the same `ZoneId` (ids are process-global) in both registries with shared
  `mounted`/`rect` signals. Every drop stays typed; no erased channel.
- Gallery page **Standup**: tickets and teammates drag in separate
  providers; a shared agenda tray built from double registration (in ~40
  lines of user-land code) accepts both.

### Fixed

- Keyboard navigation could dead-end across type-worlds: ArrowLeft from a
  zone nested under a foreign-type zone entered the foreign parent's id,
  which this world's registry can't resolve - announcements degraded to
  "zone N" and Enter silently did nothing while the payload stayed held.
  Ascend now skips unresolvable parents (falling back to the previous
  sibling), and Enter's target resolution skips a hovered id that isn't in
  the registry (falling back to the first acceptable zone).
- The Playwright web server command used `--interactive false`-style flags,
  which dx 0.7 parses as a subcommand; switched to the `=` form.
- Zero clippy warnings again under Rust 1.96, which grew two lints since
  2.1.0 shipped: a range assertion in the autoscroll tests
  (`manual_range_contains`) and the gallery home's group tuple
  (`type_complexity`, now a named `NumberedGroup` alias).

### Tests

- Runtime: cross-type nesting records the foreign parent but `ascend`
  refuses it; a bridge's dual registration shares one rect between both
  registries, hit-tests in each world, delivers each drop through its own
  typed callback, and unregisters independently.
- Browser: a real pointer drag from each world lands on the shared bridge
  zone with typed delivery, while the foreign world's zones stay dark.

## 2.1.0 - 2026-07-08

Retargeted to Dioxus **0.7 stable**. 2.0 depended on the `0.8.0-alpha.0`
pre-release; because Cargo won't unify an `0.8` pre-release with `0.7.x`,
that made the crate unusable in the many projects on shipped 0.7. The code
needed no changes — only the dependency requirement moved. Verified against
`0.7.9`: library (all features), the wasm32 web build, every gallery
example, and the full test suite compile and pass with zero warnings. The
crate still declares `dioxus` with `default-features = false, features =
["minimal"]`, so it pulls in no renderer of its own. (Stores, which back the
in-app drag state, have been part of Dioxus since 0.7.0.)

## 2.0.1 - 2026-07-08

Docs-only patch. The README packaged into 2.0.0 still carried a stale
pre-release warning line and a malformed crates.io badge link, which
rendered on crates.io and the docs.rs front page. This release ships the
refreshed README: fixed badges, the 2.0 compatibility table, a prominent
link to the live gallery, highlighted code fences, and a precise claim
about JavaScript (the library ships none of its own; the optional `web`
feature uses `web-sys` bindings for pointer capture).

## 2.0.0 - 2026-07-08

The pointer-first release. In-app drag and drop now runs entirely on pointer
events and keyboard input with typed Rust payloads; native browser drag is
reserved for the app boundary, where `DataTransfer` is the only transport.
Styling is Tailwind-ready through presence-based data attributes, and the
project website is a new multi-page gallery that teaches every pattern.

### Breaking: one input model

- In-app components are pointer + keyboard only. Removed `PointerDraggable`,
  `DragInputMode`, `DragMode::Native`, `Draggable::native`, the `input`
  props on sortable/grid/board/multiselect components, and every in-app
  native `ondrag*` branch (`DropZone`, `SortableList`, `SortableGrid`,
  `BoardSlot`, `TreeNodeTarget`, `CanvasDropZone`). `Draggable` is now the
  one in-app drag source, and there is no mode to choose.
- Native `DataTransfer` remains exactly where the app boundary requires it:
  `FileDropZone`, `ExternalDropZone`, `ExternalDragSource`,
  `external::typed`, and `AutoScroll`'s dragover listener.
- `DropOutcome` gained two fields: `mode: DragMode` (`Pointer` or
  `Keyboard`) and `grab: Point` (the pointer offset inside the element at
  pickup; `element - grab` is the landing top-left). Manual `DropOutcome`
  literals must add both.
- `ZoneId::auto` and `DragId::auto` now start at 2^32 so auto-generated ids
  can never collide with explicit `u32`-range ids and silently replace a
  neighboring zone in the registry (previously dependent on mount order).

### Styling: presence-based data attributes

- Drag state is exposed as attributes that are present while active and
  absent otherwise, so Tailwind variants like `data-dragging:opacity-50`
  and CSS `[data-over]` selectors match only active elements: `data-over` +
  `data-active` on `DropZone`, `FileDropZone` and `ExternalDropZone`,
  `data-dragging` + `data-disabled` on `Draggable`, `data-dragging` +
  `data-drop-target` on sortable/grid items, `data-active` + `data-over` on
  `BoardSlot`, `data-selected` on `SelectableDraggable`, `data-intent` on
  `TreeNodeTarget`, `data-active` on `CanvasDropZone`, and
  `data-sort-handle` on touch grips.
- Forwarded `style` props merge after functional inline styles instead of
  replacing them, so `touch-action`, overlay positioning and the grid's
  `display: grid` survive while your declarations win per property.
- New `item_class` prop on `SortableGrid` for the library-rendered tile
  wrappers; `DragOverlay` forwards `class` and friends to the ghost.

### Pointer path

- New `web` feature: real pointer capture via `web-sys`, so a mouse drag
  stays glued to its source when the cursor leaves it. Off by default; the
  core stays dependency-free with a held-button recovery fallback, and a
  formal gesture state machine (`transition`, `GesturePhase`,
  `GestureEvent`, `GestureEffect`) drives every drag.
- New `SortableList::overlay` prop: a fixed-position ghost sized from the
  measured source row while the in-flow row becomes the live gap.
- Live preview translates rows by the measured slot pitch (margins and gaps
  included) so spacing never squashes mid-drag; hover hit-tests client
  coordinates against rects measured at drag start.
- A release outside a list or grid commits no reorder, and a drop over a
  rejecting zone falls through to an accepting zone stacked beneath it.
  Missed drops re-measure zones and retry within 48px for touch.
- `AutoScroll` follows default mouse pointer drags via held-button state,
  never scrolls on passive hover, stops when the pointer leaves the
  container, and takes an optional `active` gate.
- `Ctrl`/`Cmd` at release resolves pointer drops to `Copy` and `Alt` to
  `Link` (`effective_effect` is public); keyboard drops report the selected
  zone's center in `DropOutcome::element` and honor
  `CanvasKeyboardPlacement` (`Center`, `Origin`, `Fixed`).
- `BoardSlot` registers as a real zone (pointer, touch and keyboard can
  target it) and `apply_move` adjusts same-column forward insertions.
- `TreeNodeTarget`, `BoardSlot` and `CanvasDropZone` mirror changing props
  through signals so their one-time registry callbacks always read current
  values.
- `FileFilter::content_types` gained a real MIME matcher (exact types,
  `type/*`, `*/*`, structured `*+json` suffixes, parameters and case
  ignored, malformed patterns rejected), and `extensions` normalizes dots,
  case and whitespace.

### The gallery website

- `examples/gallery/` replaces the showcase, tailwind and canvas examples:
  a multi-page site with one page per pattern (fourteen in all), each
  pairing a live demo with a numbered walkthrough, an annotated snippet, a
  "New to Dioxus?" callout and a real API reference. Deployed to GitHub
  Pages by CI.
- The design follows the KI-U paper system: paper and ink scales, one
  forest-green accent, earth tones used sparingly, Poppins for UI text and
  Geist Mono for code, no glow shadows. Code panels are inverse-ink with a
  homegrown Rust/rsx highlighter (no JavaScript highlighter).
- The landing page hero is itself a demo: draggable paper scraps on a
  `CanvasDropZone`.
- Removed `examples/showcase.rs`, `examples/tailwind.rs` and
  `examples/canvas.rs`. The Playwright suite now drives headless fixtures
  in `examples/regressions.rs` on a single dev server, covering overlay
  geometry and cleanup, outside-release cancellation for lists and grids,
  autoscroll edge behavior, canvas grab-offset placement, drop
  fall-through, the Ctrl-drag copy convention, reorder buttons inside
  sortable rows, and the native boundary paths.

## 1.0.0

Stable release. Carries the full feature set, the model helper APIs,
the docs.rs example cleanup, and the public repository metadata.



- Universal touch. Every interaction pattern now works with fingers and pens in every browser, not just the ones whose native HTML5 drag happens to support touch.
- `BoardItem` and `SelectableDraggable` wrap `PointerDraggable`, so board cards and multi-select boxes respond to touch, pen, mouse and keyboard out of the box.
- `SortableList` and `SortableGrid` run a pointer-event gesture path alongside the native one, driven by the same gesture state machine as `PointerDraggable` and hit-tested against per-row rects measured at drag start (stable pre-displacement slots, so the preview never oscillates).
- New `SortableList` prop `touch_handle`: confines touch drags to a leading grip (`[data-sort-handle]`) so rows inside scrollable lists keep finger-scrolling. Default remains whole-row, which sets `touch-action: none` on rows.
- New pure function `sortable::pointer_target` (the hit-test plus midpoint hysteresis logic), unit tested.
- New model helpers `core::apply_clone_or_move` and `core::apply_list_clone_or_move`: apply move-vs-copy drops to `HashMap<ZoneId, Vec<T>>` zone models or two plain `Vec<T>` lists, with key-based source removal and a clone hook for assigning fresh ids on copy.
- `TreeNodeTarget` registers itself in the zone registry: tree rows are now reachable by touch hit-testing and keyboard navigation. Keyboard drops land with `Into` intent. New `label` prop feeds screen-reader announcements. Touch hovers show the same live `data-intent` bands as mouse hovers.
- Pointer paths listen to `lostpointercapture` as a cancel signal, so a drag aborts cleanly if the browser revokes capture mid-gesture.
- **Fixed `sortable::displacement` leaving a phantom gap**: the source row
  now translates toward its landing slot while neighbors make room -
  previously the shifted neighbors overlapped the source (which still
  occupies its slot during a native drag), leaving a gap at the target.
  Offsets now conserve slot occupancy (they sum to zero).
- Showcase rework: drops are visibly stateful everywhere (crates land in
  bays and can be dragged onward, keyboard moves render, the tree really
  restructures, received links and loaded boxes stay visible), the
  FLIP-plus-displacement double animation is gone, and the layout gains a
  white workbench surface so the taupe reads as the floor around it.
- **Formal gesture state machine** (`core::machine`): the pointer drag
  lifecycle (Idle → Pressed → Dragging, threshold promotion, tap vs drop,
  foreign-pointer rejection, cancellation) is now a pure, exhaustively
  tested `transition` function. `PointerDraggable` drives it; you can too,
  for custom pointer interactions.
- **Nested keyboard traversal**: `DropZone`s nested inside other
  `DropZone`s discover their parents automatically (via context - zero
  configuration). Keyboard drags follow the WAI-ARIA tree convention:
  Up/Down cycle siblings at the current level, Right descends into a
  zone's children, Left ascends - falling back to plain next/previous in
  flat apps. Announcements gain nesting context ("Over Column 2, inside
  Board A."). Registry API: `ZoneRecord::parent`, `parent_of`,
  `children_of`, `step_sibling`, `first_child`.
- **Live drop preview** in `SortableList`: rows translate out of the way so
  a gap opens where the drop would land, with midpoint hysteresis to keep
  the gap from oscillating. On by default; `live_preview: false` restores
  the highlight-only behavior. Pure math exposed as `sortable::displacement`.
- **Forgiving touch drops**: `PointerDraggable` now re-measures zone rects
  on a missed drop (stale after mid-drag scroll/resize) and retries with a
  closest-center fallback (`ZoneRegistry::hit_test_closest`,
  `ZoneRegistry::measure_all`).
- **Composable drag modifiers** (`core::modifiers`): `LockAxis`, `Snap`,
  `KeepInside`, chained with `apply_modifiers` - the generalized form of
  canvas snapping, usable in custom interactions.
- **`a11y::ReorderButtons`**: headless move-up/move-down buttons emitting
  the same `SortEvent` as dragging - reordering with no drag gesture at
  all, the most robust accessibility fallback.
- `dragout` - `ExternalDragSource` / `OutboundContent`: drag text, links and
  HTML *out* of your app into other tabs and applications.
- `grid` - `SortableGrid`: 2D tile reorder (insert-and-reflow) or swap
  (dashboard) over CSS grid, plus `cell_of`/`index_of` coordinate helpers.
- `multiselect` - `use_selection`, `SelectableDraggable`, `SelectionCount`:
  select several items (click / Ctrl+click) and drag them as one
  `Vec<K>` payload.
- `animate` *(experimental)* - `FlipItem`: FLIP glide transitions on
  reorder, driven by an epoch counter.
- Modifier-key drop effects: Ctrl/Cmd forces Copy, Alt forces Link
  (file-manager convention), reflected in `dropEffect` feedback and
  `DropOutcome::effect`. Pure helper: `effective_effect`.
- Keyboard zone navigation is now **spatial** (top-to-bottom,
  left-to-right by measured rects) instead of registration order.
- Nested drag scopes: `Draggable`, `SortableList` and `SortableGrid`
  stop propagation on drag start/drop, so sortables inside sortables
  (and boards inside boards) each own their gestures.
- `DropZone` labels re-sync when the prop changes across renders.
- `sortable`: `ReorderMode` (`Insert`/`Swap`) and `apply_swap`.
- Ported to Dioxus `0.8.0-alpha.0` (also compiles on 0.7.9).
- State moved from `Signal<DragState<T>>` to `Store<DragState<T>>` for
  per-field reactivity: reading `dnd.over()` in render no longer reruns on
  pointer moves; `enter`/`leave`/`update_pointer` write through field lenses.
- Zone registry (`use_zone_registry`): every `DropZone` registers id, label,
  drop callback, acceptance filter and mounted element.
- Keyboard accessibility built into `Draggable` (Space/Enter pick up and
  drop, arrows choose a zone, Escape cancels) with `a11y::LiveRegion`
  screen-reader announcements.
- Touch/pen support: `pointer::PointerDraggable` (native HTML5 path for
  mouse, pointer-event hit-testing for touch).
- `autoscroll::AutoScroll`: edge-scrolling containers during drags, pure
  `MountedData` (no JS eval).
- `Core` (`DndProvider`, `Draggable`,
  `DropZone`, `DragOverlay`, store-context payload transport), `files`,
  `sortable`, `board`, `tree`, `canvas`, `external` modules; optional
  `serde` feature for typed `DataTransfer` interop.