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

Commit 36ca7ba

Browse files
authored
adding support for horizontal lists (#53)
1 parent f9996f6 commit 36ca7ba

Some content is hidden

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

58 files changed

+3589
-1325
lines changed

.eslintrc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@
8282
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
8383

8484
// Allowing warning and error console logging
85-
"no-console": ["error", { "allow": ["warn", "error"] }]
85+
"no-console": ["error", { "allow": ["warn", "error"] }],
86+
87+
// All blocks must be wrapped in curly braces {}
88+
// Preventing if(condition) return;
89+
// https://eslint.org/docs/rules/curly
90+
"curly": ["error", "all"]
8691
}
8792
}

README.md

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ This library is still fairly new and so there is a relatively small feature set.
2929

3030
### Currently supported feature set
3131

32-
- dragging an item within a single vertical list
32+
- vertical lists ↕
33+
- horizontal lists ↔
3334
- multiple independent lists on the one page
3435
- mouse 🐭 and **keyboard 🎹** dragging
36+
- independent nested lists (list can be a child of another list, but you cannot drag items from the parent list into a child list)
3537
- flexible height items (the draggable items can have different heights)
3638
- custom drag handle (you can drag a whole item by just a part of it)
3739
- the vertical list can be a scroll container (without a scrollable parent) or be the child of a scroll container (that also does not have a scrollable parent)
@@ -227,8 +229,10 @@ Currently the keyboard handling is hard coded. This might be changed in the futu
227229

228230
- **tab** <kbd>tab ↹</kbd> - standard browser tabbing will navigate through the `Droppable`'s. The library does not do anything fancy with `tab` while users are selecting. Once a drag has started, `tab` is blocked for the duration of the drag.
229231
- **spacebar** <kbd>space</kbd> - lift a focused `Draggable`. Also, drop a dragging `Draggable` where the drag was started with a `spacebar`.
230-
- **Up arrow** <kbd>↑</kbd> - move a `Draggable` that is dragging up on a vertical list
231-
- **Down arrow** <kbd>↓</kbd> - move a `Draggable` that is dragging down on a vertical list
232+
- **Up arrow** <kbd>↑</kbd> - move a `Draggable` that is dragging backward in a vertical list
233+
- **Down arrow** <kbd>↓</kbd> - move a `Draggable` that is dragging forward in a vertical list
234+
- **Left arrow** <kbd>←</kbd> - move a `Draggable` that is dragging backward in a horizontal list
235+
- **Right arrow** <kbd>→</kbd> - move a `Draggable` that is dragging forward in a horizontal list
232236
- **Escape** <kbd>esc</kbd> - cancel an existing drag - regardless of whether the user is dragging with the keyboard or mouse.
233237

234238
#### Limitations of keyboard dragging
@@ -278,7 +282,7 @@ In order to use drag and drop, you need to have the part of your `React` tree th
278282

279283
```js
280284
type Hooks = {|
281-
onDragStart?: (id: DraggableId, location: DraggableLocation) => void,
285+
onDragStart?: (initial: DragStart) => void,
282286
onDragEnd: (result: DropResult) => void,
283287
|}
284288

@@ -322,25 +326,35 @@ These are top level application events that you can use to perform your own stat
322326

323327
This function will get notified when a drag starts. You are provided with the following details:
324328

325-
- `id`: the id of the `Draggable` that is now dragging
326-
- `location`: the location (`droppableId` and `index`) of where the dragging item has started within a `Droppable`.
329+
### `initial: DragStart`
330+
331+
- `initial.draggableId`: the id of the `Draggable` that is now dragging
332+
- `initial.type`: the `type` of the `Draggable` that is now dragging
333+
- `initial.source`: the location (`droppableId` and `index`) of where the dragging item has started within a `Droppable`.
327334

328335
This function is *optional* and therefore does not need to be provided. It is **highly recommended** that you use this function to block updates to all `Draggable` and `Droppable` components during a drag. (See *Best `hooks` practices*)
329336

330337
**Type information**
331338

332339
```js
333-
onDragStart?: (id: DraggableId, location: DraggableLocation) => void
340+
onDragStart?: (initial: DragStart) => void
334341

335342
// supporting types
336-
type Id = string;
337-
type DroppableId: Id;
338-
type DraggableId: Id;
343+
type DragStart = {|
344+
draggableId: DraggableId,
345+
type: TypeId,
346+
source: DraggableLocation,
347+
|}
348+
339349
type DraggableLocation = {|
340350
droppableId: DroppableId,
341351
// the position of the draggable within a droppable
342352
index: number
343353
|};
354+
type Id = string;
355+
type DraggableId = Id;
356+
type DroppableId = Id;
357+
type TypeId = Id;
344358
```
345359

346360
### `onDragEnd` (required)
@@ -349,9 +363,10 @@ This function is *extremely* important and has an critical role to play in the a
349363

350364
It is provided with all the information about a drag:
351365

352-
### `result: DragResult`
366+
### `result: DropResult`
353367

354-
- `result.draggableId`: the id of the `Draggable` was dragging.
368+
- `result.draggableId`: the id of the `Draggable` that was dragging.
369+
- `result.type`: the `type` of the `Draggable` that was dragging.
355370
- `result.source`: the location that the `Draggable` started in.
356371
- `result.destination`: the location that the `Draggable` finished in. The `destination` will be `null` if the user dropped into no position (such as outside any list) *or* if they dropped the `Draggable` back into the same position that it started in.
357372

@@ -372,14 +387,16 @@ onDragEnd: (result: DropResult) => void
372387
// supporting types
373388
type DropResult = {|
374389
draggableId: DraggableId,
390+
type: TypeId,
375391
source: DraggableLocation,
376392
// may not have any destination (drag to nowhere)
377393
destination: ?DraggableLocation
378394
|}
379395

380396
type Id = string;
381-
type DroppableId: Id;
382-
type DraggableId: Id;
397+
type DroppableId = Id;
398+
type DraggableId = Id;
399+
type TypeId = Id;
383400
type DraggableLocation = {|
384401
droppableId: DroppableId,
385402
// the position of the droppable within a droppable
@@ -866,10 +883,13 @@ This library supports the standard [Atlassian supported browsers](https://conflu
866883
867884
Currently mobile is not supported. However, there are plans to add touch support in the future
868885
869-
## Community documentation translations
886+
## Translations
887+
870888
The documentation for this library is also available in other languages:
871889
- ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) **Korean**: [leehyunggeun/react-beautiful-dnd](https://github.com/LeeHyungGeun/react-beautiful-dnd-kr)
872890
891+
These translations are maintained by the community and are not reviewed or maintained by the maintainers of this library. Please raise issues on the respective translations if you would like to have them updated.
892+
873893
## Author / maintainer
874894
875895
Alex Reardon - [@alexandereardon](https://twitter.com/alexandereardon) - [email protected]

src/state/action-creators.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
Position,
1212
Dispatch,
1313
State,
14-
DropType,
14+
DropTrigger,
1515
CurrentDrag,
1616
InitialDrag,
1717
} from '../types';
@@ -203,29 +203,29 @@ export const clean = (): CleanAction => ({
203203
export type DropAnimateAction = {
204204
type: 'DROP_ANIMATE',
205205
payload: {|
206-
type: DropType,
206+
trigger: DropTrigger,
207207
newHomeOffset: Position,
208208
impact: DragImpact,
209209
result: DropResult,
210210
|}
211211
}
212212

213213
type AnimateDropArgs = {|
214-
type: DropType,
214+
trigger: DropTrigger,
215215
newHomeOffset: Position,
216216
impact: DragImpact,
217217
result: DropResult
218218
|}
219219

220220
const animateDrop = ({
221-
type,
221+
trigger,
222222
newHomeOffset,
223223
impact,
224224
result,
225225
}: AnimateDropArgs): DropAnimateAction => ({
226226
type: 'DROP_ANIMATE',
227227
payload: {
228-
type,
228+
trigger,
229229
newHomeOffset,
230230
impact,
231231
result,
@@ -269,15 +269,24 @@ export const drop = () =>
269269
}
270270

271271
const { impact, initial, current } = state.drag;
272-
const droppable: DroppableDimension = state.dimension.droppable[initial.source.droppableId];
272+
const sourceDroppable: DroppableDimension =
273+
state.dimension.droppable[initial.source.droppableId];
274+
const destinationDroppable: ?DroppableDimension = impact.destination ?
275+
state.dimension.droppable[impact.destination.droppableId] :
276+
null;
273277

274278
const result: DropResult = {
275279
draggableId: current.id,
280+
type: current.type,
276281
source: initial.source,
277282
destination: impact.destination,
278283
};
279284

280-
const scrollDiff = getScrollDiff(initial, current, droppable);
285+
const scrollDiff = getScrollDiff(
286+
initial,
287+
current,
288+
sourceDroppable,
289+
);
281290

282291
const newHomeOffset: Position = getNewHomeClientOffset({
283292
movement: impact.movement,
@@ -286,6 +295,7 @@ export const drop = () =>
286295
droppableScrollDiff: scrollDiff.droppable,
287296
windowScrollDiff: scrollDiff.window,
288297
draggables: state.dimension.draggable,
298+
axis: destinationDroppable ? destinationDroppable.axis : null,
289299
});
290300

291301
// Do not animate if you do not need to.
@@ -302,7 +312,7 @@ export const drop = () =>
302312
}
303313

304314
dispatch(animateDrop({
305-
type: 'DROP',
315+
trigger: 'DROP',
306316
newHomeOffset,
307317
impact,
308318
result,
@@ -330,6 +340,7 @@ export const cancel = () =>
330340

331341
const result: DropResult = {
332342
draggableId: current.id,
343+
type: current.type,
333344
source: initial.source,
334345
// no destination when cancelling
335346
destination: null,
@@ -345,7 +356,7 @@ export const cancel = () =>
345356
const scrollDiff = getScrollDiff(initial, current, droppable);
346357

347358
dispatch(animateDrop({
348-
type: 'CANCEL',
359+
trigger: 'CANCEL',
349360
newHomeOffset: add(scrollDiff.droppable, scrollDiff.window),
350361
impact: noImpact,
351362
result,
@@ -414,7 +425,7 @@ export const lift = (id: DraggableId,
414425
dispatch(beginLift());
415426
dispatch(requestDimensions(type));
416427

417-
// Dimensions will be requested synronously
428+
// Dimensions will be requested synchronously
418429
// after they are done - lift.
419430
// Could improve this by explicitly waiting until all dimensions are published.
420431
// Could also allow a lift to occur before all the dimensions are published

src/state/axis.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @flow
2+
import type { HorizontalAxis, VerticalAxis } from '../types';
3+
4+
export const vertical: VerticalAxis = {
5+
direction: 'vertical',
6+
line: 'y',
7+
start: 'top',
8+
end: 'bottom',
9+
size: 'height',
10+
};
11+
12+
export const horizontal: HorizontalAxis = {
13+
direction: 'horizontal',
14+
line: 'x',
15+
start: 'left',
16+
end: 'right',
17+
size: 'width',
18+
};

src/state/dimension.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// @flow
2+
import { vertical, horizontal } from './axis';
23
import type {
34
DroppableId,
45
DraggableId,
56
Position,
67
DraggableDimension,
78
DroppableDimension,
9+
Direction,
810
DimensionFragment,
911
} from '../types';
1012

@@ -112,6 +114,7 @@ export const getDraggableDimension = ({
112114
type GetDroppableArgs = {|
113115
id: DroppableId,
114116
clientRect: ClientRect,
117+
direction?: Direction,
115118
margin?: Margin,
116119
windowScroll?: Position,
117120
scroll?: Position,
@@ -120,6 +123,7 @@ type GetDroppableArgs = {|
120123
export const getDroppableDimension = ({
121124
id,
122125
clientRect,
126+
direction = 'vertical',
123127
margin = noMargin,
124128
windowScroll = origin,
125129
scroll = origin,
@@ -129,6 +133,7 @@ export const getDroppableDimension = ({
129133

130134
const dimension: DroppableDimension = {
131135
id,
136+
axis: direction === 'vertical' ? vertical : horizontal,
132137
scroll: {
133138
initial: scroll,
134139
// when we start the current scroll is the initial scroll

src/state/get-diff-to-jump-to-next-index.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22
import memoizeOne from 'memoize-one';
33
import getDraggablesInsideDroppable from './get-draggables-inside-droppable';
4+
import { patch } from './position';
45
import type {
56
DraggableLocation,
67
DraggableDimension,
@@ -9,12 +10,13 @@ import type {
910
DroppableDimensionMap,
1011
Position,
1112
DraggableId,
13+
Axis,
1214
} from '../types';
1315

1416
const getIndex = memoizeOne(
1517
(draggables: DraggableDimension[],
16-
target: DraggableDimension,
17-
): number => draggables.indexOf(target),
18+
target: DraggableDimension
19+
): number => draggables.indexOf(target)
1820
);
1921

2022
type GetDiffArgs = {|
@@ -35,6 +37,7 @@ export default ({
3537
const droppable: DroppableDimension = droppables[location.droppableId];
3638
const draggable: DraggableDimension = draggables[draggableId];
3739
const currentIndex: number = location.index;
40+
const axis: Axis = droppable.axis;
3841

3942
const insideDroppable: DraggableDimension[] = getDraggablesInsideDroppable(
4043
droppable,
@@ -66,14 +69,12 @@ export default ({
6669
(!isMovingForward && nextIndex >= startIndex);
6770

6871
const amount: number = isMovingTowardStart ?
69-
atCurrentIndex.page.withMargin.height :
70-
atNextIndex.page.withMargin.height;
72+
atCurrentIndex.page.withMargin[axis.size] :
73+
atNextIndex.page.withMargin[axis.size];
7174

72-
const diff: Position = {
73-
// not worrying about horizontal for now
74-
x: 0,
75-
y: isMovingForward ? amount : -amount,
76-
};
75+
const signed: number = isMovingForward ? amount : -amount;
76+
77+
const diff: Position = patch(axis.line, signed);
7778

7879
return diff;
7980
};

0 commit comments

Comments
 (0)