Skip to content
This repository was archived by the owner on Aug 18, 2025. It is now read-only.

Commit 828be4a

Browse files
authored
Adding a multi drag pattern (#383)
1 parent 702f699 commit 828be4a

File tree

22 files changed

+1177
-27
lines changed

22 files changed

+1177
-27
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ We have created upgrade instructions in our release notes to help you upgrade to
5353
- Movement between lists (▤ ↔ ▤)
5454
- Mouse 🐭, keyboard 🎹 and touch 👉📱 (mobile, tablet and so on) support
5555
- Auto scrolling - automatically scroll containers and the window as required during a drag (even with keyboard 🔥)
56+
- [Multi drag support](/docs/patterns/multi-drag.md)
5657
- Incredible screen reader support - we provide an amazing experience for english screen readers out of the box 📦. We also provide complete customisation control and internationalisation support for those who need it 💖
5758
- Conditional [dragging](https://github.com/atlassian/react-beautiful-dnd#props-1) and [dropping](https://github.com/atlassian/react-beautiful-dnd#conditionally-dropping)
5859
- Multiple independent lists on the one page
@@ -308,6 +309,12 @@ class App extends React.Component {
308309
}
309310
```
310311

312+
## Multi drag
313+
314+
We have created a [multi drag pattern](/docs/patterns/multi-drag.md) that you can build on top of `react-beautiful-dnd` in order to support dragging multiple `Draggable` items at once.
315+
316+
![multi drag demo](https://user-images.githubusercontent.com/2182637/37322724-7843a218-26d3-11e8-9ebb-8d5853387bb3.gif)
317+
311318
## Preset styles
312319

313320
We apply a number of non-visible styles to facilitate the dragging experience. We do this using combination of styling targets and techniques. It is a goal of the library to provide unopinioned styling. However, we do apply some reasonable `cursor` styling on drag handles by default. This is designed to make the library work as simply as possible out of the box. If you want to use your own cursors you are more than welcome to. All you need to do is override our cursor style rules by using a rule with [higher specificity](https://css-tricks.com/specifics-on-css-specificity/).

docs/guides/how-we-use-dom-events.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ Some user events have a direct impact on a drag: such as a `mousemove` when drag
5656

5757
### Initial `mousedown`
5858

59-
- `preventDefault()` not called on `mousedown`
59+
- `preventDefault()` **is called on `mousedown`** 😞
6060

61-
When the user first performs a `mousedown` on a *drag handle* we are not sure if they are intending to click or drag. Because at this stage we are not sure, we do not call `preventDefault()` on the event.
61+
> This is the only known exception to our rule set. It is unfortunate that it is the first one to appear in this guide!
62+
63+
When the user first performs a `mousedown` on a *drag handle* we are not sure if they are intending to click or drag. Ideally we would not call `preventDefault()` on this event as we are not sure if it is a part of a drag. However, we need to call `preventDefault()` in order to avoid the item obtaining focus as it has a `tabIndex`.
6264

6365
### We are not sure yet if a drag will start
6466

docs/patterns/multi-drag.md

Lines changed: 287 additions & 0 deletions
Large diffs are not rendered by default.

src/debug/timings.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ type Records = {
66

77
const records: Records = {};
88

9-
const flag: string = '__react-beautiful-dnd-debug-hook__';
10-
11-
// temp
12-
// window[flag] = true;
9+
const flag: string = '__react-beautiful-dnd-debug-timings-hook__';
1310

1411
const isTimingsEnabled = (): boolean => Boolean(window[flag]);
1512

src/view/drag-drop-context/drag-drop-context.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export default class DragDropContext extends React.Component<Props> {
170170
this.styleMarshal.onPhaseChange(current);
171171
}
172172

173-
const isDragEnding: boolean = current.phase !== 'DRAGGING' && previousInThisExecution.phase === 'DRAGGING';
173+
const isDragEnding: boolean = previousInThisExecution.phase === 'DRAGGING' && current.phase !== 'DRAGGING';
174174

175175
// in the case that a drag is ending we need to instruct the dimension marshal
176176
// to stop listening to changes. Otherwise it will try to process

src/view/drag-handle/sensor/create-mouse-sensor.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type State = {|
2525
|}
2626

2727
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
28-
const primaryButton = 0;
28+
const primaryButton: number = 0;
2929
const noop = () => { };
3030

3131
// shared management of mousedown without needing to call preventDefault()
@@ -256,10 +256,16 @@ export default ({
256256
// Registering that this event has been handled.
257257
// This is to prevent parent draggables using this event
258258
// to start also.
259-
// Not using preventDefault() as we are not sure
259+
// Ideally we would not use preventDefault() as we are not sure
260260
// if this mouse down is part of a drag interaction
261+
// Unfortunately we do to prevent the element obtaining focus (see below).
261262
mouseDownMarshal.handle();
262263

264+
// Unfortunately we do need to prevent the drag handle from getting focus on mousedown.
265+
// This goes against our policy on not blocking events before a drag has started.
266+
// See [How we use dom events](/docs/guides/how-we-use-dom-events.md).
267+
event.preventDefault();
268+
263269
const point: Position = {
264270
x: clientX,
265271
y: clientY,

stories/9-multi-drag-story.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @flow
2+
import React from 'react';
3+
import { storiesOf } from '@storybook/react';
4+
import TaskApp from './src/multi-drag/task-app';
5+
6+
storiesOf('Multi drag', module)
7+
.add('pattern', () => (
8+
<TaskApp />
9+
));

stories/src/accessible/data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @flow
2-
import type { Task } from './types';
2+
import type { Task } from '../types';
33

44
const tasks: Task[] = [
55
{

stories/src/accessible/task-app.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
DraggableLocation,
1515
HookProvided,
1616
} from '../../../src/';
17-
import type { Task } from './types';
17+
import type { Task } from '../types';
1818

1919
type State = {|
2020
tasks: Task[],

stories/src/accessible/task-list.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import React, { Component } from 'react';
33
import styled from 'styled-components';
44
import { Droppable } from '../../../src/';
55
import Task from './task';
6-
import type { DroppableProvided, DroppableStateSnapshot } from '../../../src/';
7-
import type { Task as TaskType } from './types';
6+
import type { DroppableProvided } from '../../../src/';
7+
import type { Task as TaskType } from '../types';
88
import { colors, grid, borderRadius } from '../constants';
99

1010
type Props = {|
@@ -14,7 +14,7 @@ type Props = {|
1414

1515
const Container = styled.div`
1616
width: 300px;
17-
background-color: ${colors.grey};
17+
background-color: ${colors.grey.dark};
1818
border-radius: ${borderRadius}px;
1919
`;
2020

0 commit comments

Comments
 (0)