Skip to content

Commit 1b847ec

Browse files
authored
Merge pull request #57 from samiodeh1337/refactor/v3
feat: v3 — multi-select, group drag, drag events, new docs
2 parents 135f04a + a1f4e43 commit 1b847ec

168 files changed

Lines changed: 21162 additions & 4188 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ jobs:
2828
- name: Lint
2929
run: npm run lint
3030

31+
- name: Unit tests
32+
run: npm test
33+
3134
- name: Build library
3235
run: npm run build
3336

‎.github/workflows/publish.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ jobs:
2727
- name: Lint
2828
run: npm run lint
2929

30+
- name: Unit tests
31+
run: npm test
32+
3033
- name: Build library
3134
run: npm run build
3235

‎.gitignore‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@ docs-dist
2323
*.njsproj
2424
*.sln
2525
*.sw?
26+
27+
# local snapshots, never committed
28+
Archive.zip
29+
*.tgz
30+
31+
# local only: browser test scripts and working notes
32+
scripts/e2e/
33+
docs/WALKTHROUGH-3.0.0.md
34+
HANDOFF.md

‎ARCHITECTURE.md‎

Lines changed: 126 additions & 54 deletions
Large diffs are not rendered by default.

‎CHANGELOG.md‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,79 @@
11
# 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)
277

378
## 2.0.18 (2026-07-17)
479

‎CONTRIBUTING.md‎

Lines changed: 19 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,33 @@
1-
# Contributing to react-table-dnd
1+
# Contributing
22

3-
Thank you for your interest in contributing! This guide will help you get started.
4-
5-
## Getting Started
6-
7-
### Prerequisites
8-
9-
- Node.js >= 18
10-
- npm >= 9
11-
12-
### Setup
13-
14-
1. **Fork** the repository on GitHub
15-
2. **Clone** your fork locally:
16-
```bash
17-
git clone https://github.com/<your-username>/react-table-dnd.git
18-
cd react-table-dnd
19-
```
20-
3. **Install** dependencies:
21-
```bash
22-
npm install
23-
```
24-
4. **Start** the dev server:
25-
```bash
26-
npm run dev
27-
```
28-
29-
## Development Workflow
30-
31-
### Branch Naming
32-
33-
Create a branch from `main` using one of these prefixes:
34-
35-
| Prefix | Use for |
36-
| ------------ | ------------------------------ |
37-
| `feat/` | New features |
38-
| `fix/` | Bug fixes |
39-
| `refactor/` | Code refactoring |
40-
| `docs/` | Documentation changes |
41-
| `chore/` | Tooling, CI, dependency updates |
42-
43-
Example: `feat/virtual-scroll-improvements`
44-
45-
### Commit Messages
46-
47-
We follow [Conventional Commits](https://www.conventionalcommits.org/):
3+
Node 18 or newer.
484

5+
```bash
6+
git clone https://github.com/<you>/react-table-dnd.git
7+
cd react-table-dnd
8+
npm install
9+
npm run dev # docs site with the live demos
10+
npm test # unit tests
11+
npm run lint && npm run format:check && npm run build
4912
```
50-
<type>(<scope>): <short description>
5113

52-
[optional body]
14+
Prettier runs on commit.
5315

54-
[optional footer]
55-
```
16+
## Branches and commits
5617

57-
**Types:** `feat`, `fix`, `refactor`, `docs`, `chore`, `test`, `perf`, `style`
18+
Branch from `main` with one of `feat/`, `fix/`, `refactor/`, `docs/`, `chore/`, for example `feat/virtual-scroll`.
5819

59-
Examples:
60-
- `feat(drag): add horizontal auto-scroll support`
61-
- `fix(column): prevent drag outside container bounds`
62-
- `docs: update README with virtual scroll example`
20+
Commits follow [Conventional Commits](https://www.conventionalcommits.org/): `feat(drag): add horizontal auto-scroll`, `fix(column): keep the drag inside the container`, `docs: virtual scroll example`.
6321

64-
### Code Style
22+
## Pull requests
6523

66-
- Code is auto-formatted with **Prettier** on commit via Husky + lint-staged
67-
- Run `npm run format` to format all files manually
68-
- Run `npm run lint` to check for linting errors
69-
- Run `npm run format:check` to verify formatting without writing
24+
- One feature or fix per PR.
25+
- Add user-facing changes to `CHANGELOG.md`.
7026

71-
### Making Changes
27+
## Bugs and features
7228

73-
1. Create your feature branch: `git checkout -b feat/my-feature`
74-
2. Make your changes
75-
3. Ensure your code is formatted: `npm run format`
76-
4. Ensure linting passes: `npm run lint`
77-
5. Build the library to check for errors: `npm run build`
78-
6. Commit your changes following the commit convention above
79-
7. Push to your fork: `git push origin feat/my-feature`
80-
8. Open a **Pull Request** against `main`
81-
82-
## Pull Request Guidelines
83-
84-
- Fill out the PR template completely
85-
- Keep PRs focused — one feature or fix per PR
86-
- Update `CHANGELOG.md` for user-facing changes
87-
- Ensure CI checks pass
88-
- Be responsive to review feedback
89-
90-
## Reporting Bugs
91-
92-
Use the [Bug Report](https://github.com/samiodeh1337/react-table-dnd/issues/new?template=bug_report.md) template. Include:
93-
94-
- Steps to reproduce
95-
- Expected vs actual behavior
96-
- A minimal reproduction (CodeSandbox/StackBlitz link is ideal)
97-
98-
## Requesting Features
99-
100-
Use the [Feature Request](https://github.com/samiodeh1337/react-table-dnd/issues/new?template=feature_request.md) template. Describe:
101-
102-
- The problem you're trying to solve
103-
- Your proposed solution
104-
- Any alternatives you've considered
105-
106-
## Project Structure
107-
108-
```
109-
src/
110-
├── Components/ # Core table components
111-
│ ├── TableContainer/ # Main container + useTable hook
112-
│ ├── TableHeader.tsx # Header row
113-
│ ├── TableBody.tsx # Body rows
114-
│ ├── BodyRow.tsx # Individual row
115-
│ ├── ColumnCell.tsx # Header cell
116-
│ ├── RowCell.tsx # Body cell
117-
│ ├── Draggable.tsx # Drag wrapper
118-
│ ├── DragHandle.tsx # Drag handle component
119-
│ └── index.ts # Public exports
120-
├── hooks/ # Custom hooks
121-
│ ├── useAutoScroll.ts
122-
│ ├── useDragContextEvents.tsx
123-
│ └── useLongPress.ts
124-
└── examples/ # Demo examples (docs site)
125-
```
29+
Use the [bug report](https://github.com/samiodeh1337/react-table-dnd/issues/new?template=bug_report.md) or [feature request](https://github.com/samiodeh1337/react-table-dnd/issues/new?template=feature_request.md) template. For a bug, a minimal reproduction (CodeSandbox or StackBlitz) helps most.
12630

12731
## License
12832

129-
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
33+
Contributions are licensed under the [MIT License](LICENSE).

0 commit comments

Comments
 (0)