|
1 | 1 | # Changelog |
| 2 | +## 3.0.0 (2026-10-02) |
| 3 | + |
| 4 | +### Breaking / behaviour changes |
| 5 | + |
| 6 | +- Drop resolution changed for every drag: drops follow the placeholder more precisely. `targetIndex` keeps its 2.x meaning (the index `arrayMove(data, sourceIndex, targetIndex)` expects), but the same pointer position can give a different index than in 2.x |
| 7 | +- Row drags always report `selectedIds`, `sourceIndices` and `insertIndex` in `DragEndResult`, even on tables without `selectable` (a test that compares the whole result object will see the extra fields) |
| 8 | +- `DragEndResult` is now a union on `dragType` (`RowDragEndResult | ColumnDragEndResult`): after `if (r.dragType === 'row')` the three row fields are typed as always present, so no `!` is needed. Code that builds a row result object by hand (e.g. in tests) must include them |
| 9 | +- `BodyRow` and `ColumnCell` now declare `id` and `index` as required props in their types (they were always required at runtime; omitting them used to compile and produce a row id of `"undefined"`) |
| 10 | +- `BodyRow`, `ColumnCell` and `RowCell` no longer accept any prop name in their types (the `[propName: string]: any` signature is gone; unknown props were silently dropped at runtime). Real HTML attributes (`aria-*`, `data-*`, `title`, `onClick`, …) are now typed and passed to the row / header cell / cell element; `TableContainer`, `TableHeader`, `TableBody`, `DragHandle` and `SelectHandle` pass them to their element too |
| 11 | +- `DraggableProps` is no longer exported (it described an internal component); every public component's props type is exported instead: `TableContainerProps`, `TableHeaderProps`, `ColumnCellProps`, `TableBodyProps`, `BodyRowProps`, `RowCellProps`, `DragHandleProps`, `SelectHandleProps` |
| 12 | +- The drag card is `[data-rtdnd='clone']` instead of `id="portalroot"` (two tables on one page rendered the same id); update CSS that targets `#portalroot` |
| 13 | +- Rows get `data-selected="true"` while selected whenever `selectedIds` or `defaultSelectedIds` is passed |
| 14 | +- The CommonJS build is now `dist/index.cjs` (was `dist/index.cjs.js`); `require('react-table-dnd')` resolves to it through `exports`. Only deep imports of the old file path are affected |
| 15 | +- With `selectable` on, a row drag starts after the pointer has moved 5px (a click never flashes the clone); non-selectable tables and columns still start on mousedown |
| 16 | +- Escape during a drag now cancels the drag only: it no longer reaches your own Escape handlers (a surrounding dialog stays open) |
| 17 | +- Presses in a text field, textarea, select or contenteditable inside a row no longer start a drag |
| 18 | +- The v2 README, architecture notes and examples are archived under `docs/v2/` |
| 19 | + |
| 20 | +### Features |
| 21 | + |
| 22 | +- Multi-select rows: `selectable`, `selectedIds`, `defaultSelectedIds`, `onSelectionChange` on `TableContainer`; click, Ctrl/Cmd/Alt+click and Shift+click on desktop, tap on touch; pressing empty body space or Escape (when your last press was in the table) clears the selection |
| 23 | +- Group drag: dragging a selected row moves the whole selection; you drag a compact card with a count badge, the other selected rows fold into the card as the drag starts (360ms), and on drop they unfold out of the landing slot into place (260ms); both animations are skipped under `prefers-reduced-motion` |
| 24 | +- `DragEndResult` gains `selectedIds`, `sourceIndices` and `insertIndex`; `RowDragEndResult` / `ColumnDragEndResult` types; new `moveRowsById` and `arrayMoveMultiple` helpers; `SelectionState` type exported |
| 25 | +- `disabled` on `BodyRow` and `ColumnCell` works: that item cannot be picked up (it was typed but ignored in 2.x) |
| 26 | +- Drag lifecycle callbacks on `TableContainer`: `onDragStart`, `onDragOver` (each new drop slot, with what a drop there would give) and `onDragCancel` (Escape, window blur, a release without moving). New `DragStartInfo` and `DragCancelInfo` types. Every event, `DragEndResult` included, carries `id`: the id of the grabbed row or column |
| 27 | +- Docs: a TanStack Table guide and a **TanStack Table** demo (checkboxes, a filter, drops that keep hidden rows in place; `@tanstack/react-table` 9 is a dev dependency of the docs only), and a Drag events guide with a live event log |
| 28 | +- `showDragCount` prop on `TableContainer` (default `true`) to hide the count badge on the group drag card |
| 29 | +- `SelectHandle` component: checkbox-style selection — only presses inside the handle change the selection for that row |
| 30 | +- `BodyRow` gains `selectedClassName` / `selectedStyle` and sets `data-selected="true"` while selected |
| 31 | +- Styling hooks: `[data-rtdnd='drag-count']` (badge), `[data-rtdnd='drag-stack']` (ghost cards), `--rtdnd-card-bg` (card colour), `[data-drop-target]` (the row/column the drop will land on) |
| 32 | +- `options.rowDragRange` and `options.columnDragRange` are now optional in the type (either can be left out) |
| 33 | +- Demos: new Multi-Select and Virtual + Multi-Select demos; checkbox (`SelectHandle`) selection in Showcase and Custom Heights; click selection in Tailwind, styled-components, className & style, Fixed Sizes, Drag Handle, Column Widths, Scrollable Cells and Drag Ranges |
| 34 | + |
| 35 | +### Changed |
| 36 | + |
| 37 | +- Drops are consistent in both directions and with mixed row heights and column widths. Previously moving an item forward reacted much sooner than moving it back, which felt uneven with mixed column widths |
| 38 | +- Under `prefers-reduced-motion` the drop is applied immediately instead of after the 200ms snap |
| 39 | + |
| 40 | +### Bug Fixes |
| 41 | + |
| 42 | +- `require('react-table-dnd')` threw `exports is not defined in ES module scope` (the CommonJS build was a `.js` file in a `"type": "module"` package) |
| 43 | +- Types did not resolve under `moduleResolution: node16` / `nodenext` (extension-less relative imports); the types are now one self-contained file, with a `.d.cts` copy for `require` |
| 44 | +- Server-side rendering failed with "Missing getServerSnapshot", and React 18 printed a `useLayoutEffect` warning for every table rendered on the server |
| 45 | +- The published types needed `esModuleInterop` / `allowSyntheticDefaultImports`; without them every component's props silently became `any` |
| 46 | +- A dragged row lost its grab cursor for good after the drop (the engine cleared the inline cursor React had set) |
| 47 | +- A drag erased inline styles you set yourself: `opacity`, `transform` and `transition` on `RowCell`s after a column drag, and `touch-action` on `TableContainer`; a column drop also left the engine's transition on half the body cells |
| 48 | +- During auto-scroll several rows could carry `[data-drop-target]` at once |
| 49 | +- A drag kept following the pointer after the mouse button was released outside the window or the window lost focus; it now cancels |
| 50 | +- Buttons, links and other controls inside a row never got their clicks in a table without `selectable`: the press started a drag at once. A press on a control now waits for a 5px move before dragging |
| 51 | +- With `selectable`, a row could not be dragged by pressing an image or a link in it: the browser's own drag of the image took the pointer |
| 52 | +- A selected row removed from the data (not scrolled away in a virtual table) still counted in the group: the drag card showed the wrong count and `selectedIds` listed the missing id |
| 53 | +- Shift+click could start a range from a row that was no longer on screen or selected (for example after a filter hid it and the selection was cleared); it now starts a new range |
| 54 | +- Touch: lifting a second finger dropped the drag; a long-press that turned into a click (off a drag handle) could start an auto-scroll that never stopped; a swipe on a row could not scroll the page once the table body reached its edge |
| 55 | +- Scrolling the page, a parent or the table body without moving the pointer (a wheel, a sideways scroll) or resizing the window during a drag left the drop slot where the rows used to be |
| 56 | +- Virtual tables: rows that mounted during a column drag were neither hidden, shifted nor shown in the drag card |
| 57 | +- Escape stopped clearing the selection when the table sat inside a focusable element (a dialog, a `tabIndex={-1}` wrapper): clicking plain cell text focuses that wrapper, which counted as leaving the table |
| 58 | +- Touch scrolling did not work on rows or columns locked by `rowDragRange` / `columnDragRange` |
| 59 | +- Passing a `ref` to `TableHeader` broke column drag and header/body scroll sync |
| 60 | +- An element with its own `data-id` attribute inside a cell was mistaken for a draggable row |
| 61 | +- The idle drag clone was not hidden, so browsers with always-visible scrollbars could paint empty scrollbars in the top-left corner |
| 62 | +- Escape during a drag also closed surrounding dialogs, and Escape during the 5px wait cleared the selection |
| 63 | +- Escape during the 200ms drop snap reported the drag a second time with no target |
| 64 | +- A press during the 200ms drop snap was resolved against the pre-drop layout (wrong row, wrong Shift range) |
| 65 | +- A timer from a previous tap could re-enable mouse handling during the next tap on touch devices |
| 66 | +- `rowDragRange: { end: 0 }` was ignored when resolving drop slots |
| 67 | +- Escape during a drag now restores the dragged row / column cells instead of leaving them invisible |
| 68 | +- A press exactly on an SVG shape inside a `DragHandle` (e.g. one of the grip dots) no longer gets mistaken for a scrollbar click and ignored |
| 69 | +- Starting a new drag while the previous drop is still snapping into place now finalizes that drop before measuring the pressed row |
| 70 | +- `DragRange.end` was documented as exclusive; it is inclusive |
| 71 | + |
| 72 | +### Internals |
| 73 | + |
| 74 | +- The drag engine was split: one drag model (every drag is a `DragGroup`, a single row is a group of one), pure decision modules under `src/hooks/drag/` (selection rules, group resolution, drop maths, shift maths), and small hooks for gestures, deferred activation, the clone, hidden elements, the fold and the settle animation. `useDragContextEvents` only sequences them |
| 75 | +- Unit tests with vitest (`npm test`) for the pure modules and the `arrayMoveMultiple` / `moveRowsById` helpers, run in CI before the build |
| 76 | +- `styled-components` and `classnames` removed from the bundler externals (neither was used) |
2 | 77 |
|
3 | 78 | ## 2.0.18 (2026-07-17) |
4 | 79 |
|
|
0 commit comments