Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 16 upgrade #406

Merged
merged 64 commits into from
Apr 11, 2018
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
1aecb36
upgrading react
alexreardon Mar 23, 2018
a239603
updating enzyme
alexreardon Mar 23, 2018
23265cf
adding latest flowtyped
alexreardon Mar 23, 2018
dc1c883
updating ssr snapshot
alexreardon Mar 23, 2018
ee4b62c
fixing window bindings test
alexreardon Mar 25, 2018
0f14e89
fixing drag handle tests
alexreardon Mar 25, 2018
85dc4d5
improved placeholder
alexreardon Mar 26, 2018
866d8a9
adding react-dom and a peer dep
alexreardon Mar 26, 2018
6849915
progress
alexreardon Mar 27, 2018
bdc7a7d
story point in time
alexreardon Mar 27, 2018
10110c4
progress
alexreardon Mar 27, 2018
e4084f6
progress
alexreardon Mar 28, 2018
f1d8072
removing React.Portal as a public api
alexreardon Mar 29, 2018
bca56b4
Merge branch 'master' of github.com:atlassian/react-beautiful-dnd int…
alexreardon Apr 3, 2018
c10645b
updating comments
alexreardon Apr 4, 2018
dbd17de
removing unneeded touchmove handler
alexreardon Apr 4, 2018
190160f
moving away from deprecated lifecycle methods
alexreardon Apr 4, 2018
820c735
upgrading to react 16.3
alexreardon Apr 5, 2018
26084ab
cleaning up table example
alexreardon Apr 5, 2018
2f00bee
upgrading invariant
alexreardon Apr 5, 2018
a29f747
rearranging lifecycle methods
alexreardon Apr 5, 2018
098ff97
removing componentWillMount from DragDropContext
alexreardon Apr 5, 2018
6817c29
fixing some tests
alexreardon Apr 5, 2018
5ecf037
fixing tests for unconnected-draggable
alexreardon Apr 5, 2018
a8347de
removing unused on element touchmove test
alexreardon Apr 5, 2018
c865e54
updating draggable-dimension-publisher tests
alexreardon Apr 5, 2018
393024a
updating tests
alexreardon Apr 5, 2018
ed2e2ab
fixing up comments
alexreardon Apr 5, 2018
4a31794
fixing droppable dimension test
alexreardon Apr 5, 2018
6a2ee84
fixing connected-droppable tests
alexreardon Apr 5, 2018
fdb91ff
updating dimension marshal tests
alexreardon Apr 6, 2018
7310391
no more logs in integration test
alexreardon Apr 6, 2018
8efa9fc
fixing browser integration test
alexreardon Apr 6, 2018
4a371ee
renaming browser test
alexreardon Apr 6, 2018
c666f04
improving test name
alexreardon Apr 6, 2018
b19dfad
Merge branch 'master' of github.com:atlassian/react-beautiful-dnd int…
alexreardon Apr 6, 2018
abd2a39
fixing linting
alexreardon Apr 6, 2018
d257a75
removing placeholder from draggable examples
alexreardon Apr 6, 2018
8fd4036
updating readme
alexreardon Apr 6, 2018
31744b8
adding a story for portals
alexreardon Apr 6, 2018
eb7195f
test for portal focus management
alexreardon Apr 8, 2018
d1cec09
improving portal story
alexreardon Apr 8, 2018
350eb9a
fixing lint
alexreardon Apr 8, 2018
42d4bcb
adding some love to table story
alexreardon Apr 8, 2018
431f417
adding copy table button to story
alexreardon Apr 8, 2018
d69574b
adding guide for using portals
alexreardon Apr 8, 2018
ec03035
doc updates
alexreardon Apr 9, 2018
0ba08a2
adding example that uses tables and portals
alexreardon Apr 9, 2018
0bf62e0
Merge branch 'master' of github.com:atlassian/react-beautiful-dnd int…
alexreardon Apr 9, 2018
c1a6a99
updating docs. fixing flow
alexreardon Apr 9, 2018
9b19065
using basic data again
alexreardon Apr 9, 2018
704f5a0
updating types in readme
alexreardon Apr 10, 2018
27643d0
Merge branch 'master' of github.com:atlassian/react-beautiful-dnd int…
alexreardon Apr 10, 2018
e8d5f23
cleanup
alexreardon Apr 10, 2018
acaec94
creating seperate command for checking the size of the bundles
alexreardon Apr 10, 2018
23e413c
adding extra info to rollup config
alexreardon Apr 11, 2018
eecb5c7
updating comment
alexreardon Apr 11, 2018
a9e6aaa
removing flow from rollup config
alexreardon Apr 11, 2018
d1abdef
cleaner usage of npm scripts
alexreardon Apr 11, 2018
9aa5374
Merge branch 'master' of github.com:atlassian/react-beautiful-dnd int…
alexreardon Apr 11, 2018
1a5e0ec
adding new scripts to check and unpdate bundle sizes
alexreardon Apr 11, 2018
2cbe25f
temporarily moving away from a seperate prepublishOnly step for consi…
alexreardon Apr 11, 2018
8056b5c
cleaning up bundle size check flow
alexreardon Apr 11, 2018
864b85f
adding more meta
alexreardon Apr 11, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dist/
lib/
esm/
flow-typed/
site/

# autoprefixer's eslintConfig was being picked up and attempted to be applied. No thanks
website/node_modules/
Expand Down
17 changes: 16 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
// This was turned off for wc - but should be re-enabled eventually
"react/no-unknown-property": ["off"],

// Allowing components to have multiple components in it
"react/no-multi-comp": "off",

// http://eslint.org/docs/rules/no-multiple-empty-lines
// Disallow more than 1 empty lines
"no-multiple-empty-lines": ["error", { "max": 1 }],
Expand Down Expand Up @@ -112,6 +115,18 @@
"function-paren-newline": "off",

// Require // @flow at the top of files
"flowtype/require-valid-file-annotation": ["error", "always", { "annotationStyle": "line" }]
"flowtype/require-valid-file-annotation": ["error", "always", { "annotationStyle": "line" }],

// Forcing consistent use of 'it' for jest
"jest/consistent-test-it": ["error", {"fn": "it"}],

// No .only tests allowed
"jest/no-focused-tests": "error",

// Must actually assert something in your jest test
"jest/valid-expect": "error",

// Force valid describe blocks
"jest/valid-describe": "error"
}
}
129 changes: 46 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ We have created upgrade instructions in our release notes to help you upgrade to
- Conditional [dragging](https://github.com/atlassian/react-beautiful-dnd#props-1) and [dropping](https://github.com/atlassian/react-beautiful-dnd#conditionally-dropping)
- Multiple independent lists on the one page
- Flexible item sizes - the draggable items can have different heights (vertical lists) or widths (horizontal lists)
- Compatible with semantic table reordering - [guide](/docs/guides/tables.md)
- Custom drag handles - you can drag a whole item by just a part of it
- A `Droppable` 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)
- Independent nested lists - a list can be a child of another list, but you cannot drag items from the parent list into a child list
Expand Down Expand Up @@ -893,15 +894,12 @@ import { Draggable } from 'react-beautiful-dnd';

<Draggable draggableId="draggable-1" type="PERSON" index={0}>
{(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<h4>My draggable</h4>
</div>
{provided.placeholder}
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<h4>My draggable</h4>
</div>
)}
</Draggable>;
Expand All @@ -918,10 +916,8 @@ import { Draggable } from 'react-beautiful-dnd';
{this.props.items.map((item, index) => (
<Draggable draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div>
<div ref={provided.innerRef} {...provided.draggableProps}>
{item.content}
</div>
<div ref={provided.innerRef} {...provided.draggableProps}>
{item.content}
</div>
)}
</Draggable>
Expand All @@ -932,23 +928,20 @@ import { Draggable } from 'react-beautiful-dnd';
- `isDragDisabled`: An *optional* flag to control whether or not the `Draggable` is permitted to drag. You can use this to implement your own conditional drag logic. It will default to `false`.
- `disableInteractiveElementBlocking`: An *optional* flag to opt out of blocking a drag from interactive elements. For more information refer to the section *Interactive child elements within a `Draggable`*

### Children function
### Children function (render props)

The `React` children of a `Draggable` must be a function that returns a `ReactElement`.

```js
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
Drag me!
</div>
{provided.placeholder}
</div>
)}
</Draggable>;
```
Expand Down Expand Up @@ -1018,10 +1011,8 @@ type NotDraggingStyle = {|
```js
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div>
<div ref={provided.innerRef} {...provided.draggableProps}>
Drag me!
</div>
<div ref={provided.innerRef} {...provided.draggableProps}>
Drag me!
</div>
)}
</Draggable>;
Expand All @@ -1035,7 +1026,7 @@ It is a contract of this library that it owns the positioning logic of the dragg

`react-beautiful-dnd` uses `position: fixed` to position the dragging element. This is quite robust and allows for you to have `position: relative | absolute | fixed` parents. However, unfortunately `position:fixed` is [impacted by `transform`](http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/) (such as `transform: rotate(10deg);`). This means that if you have a `transform: *` on one of the parents of a `Draggable` then the positioning logic will be incorrect while dragging. Lame! For most consumers this will not be an issue.

This will be changing soon as we move to a [portal solution](https://github.com/atlassian/react-beautiful-dnd/issues/192) where we will be appending the `Draggable` to the end of the body to avoid any parent transforms. If you really need this feature right now we have [created an example](https://www.webpackbin.com/bins/-L-3aZ_bTMiGPl8bqlRB) where we implement a portal on top of the current api. Please note however, this technique is not officially supported and might break in minor / patch releases.
To get around this you can use [`React.Portal`](https://reactjs.org/docs/portals.html). We do not enable this functionality by default as it has performance problems. We have a [using a portal guide](/guides/using-a-portal.md) explaining the performance problem in more detail and how you can set up your own `React.Portal` if you want to.

##### Extending `DraggableProps.style`

Expand All @@ -1053,14 +1044,12 @@ If you are overriding inline styles be sure to do it after you spread the `provi
...provided.draggableProps.style,
};
return (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
style={style}
>
Drag me!
</div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
style={style}
>
Drag me!
</div>
);
}}
Expand Down Expand Up @@ -1141,15 +1130,12 @@ type DragHandleProps = {|
```js
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
Drag me!
</div>
{provided.placeholder}
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
Drag me!
</div>
)}
</Draggable>;
Expand All @@ -1162,12 +1148,9 @@ Controlling a whole draggable by just a part of it
```js
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div>
<div ref={provided.innerRef} {...provided.draggableProps}>
<h2>Hello there</h2>
<div {...provided.dragHandleProps}>Drag handle</div>
</div>
{provided.placeholder}
<div ref={provided.innerRef} {...provided.draggableProps}>
<h2>Hello there</h2>
<div {...provided.dragHandleProps}>Drag handle</div>
</div>
)}
</Draggable>;
Expand Down Expand Up @@ -1196,40 +1179,19 @@ const myOnMouseDown = event => console.log('mouse down on', event.target);
})();

return (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onMouseDown={onMouseDown}
>
Drag me!
</div>
{provided.placeholder}
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onMouseDown={onMouseDown}
>
Drag me!
</div>
);
}}
</Draggable>;
```

- `provided.placeholder (?ReactElement)` The `Draggable` element has `position: fixed` applied to it while it is dragging. The role of the `placeholder` is to sit in the place that the `Draggable` was during a drag. It is needed to stop the `Droppable` list from collapsing when you drag. It is advised to render it as a sibling to the `Draggable` node. This is unlike `Droppable` where the `placeholder` needs to be *within* the `Droppable` node. When the library moves to `React` 16 the `placeholder` will be removed from api.

##### `placeholder` example

```js
<Draggable draggableId="draggable-1" index={0}>
{(provided, snapshot) => (
<div>
<div ref={provided.innerRef} {...provided.draggableProps}>
Drag me!
</div>
{/* Always render me - I will be null if not required */}
{provided.placeholder}
</div>
)}
</Draggable>;
```

#### 2. Snapshot: (DraggableStateSnapshot)**

```js
Expand All @@ -1251,22 +1213,23 @@ The `children` function is also provided with a small amount of state relating t
};

return (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={style}
>
Drag me!
</div>
{provided.placeholder}
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={style}
>
Drag me!
</div>
);
}}
</Draggable>;
```

### `Draggable` placeholder

When dragging a `Draggable` we leave behind a *placeholder* `React.Element` to maintain space in the `Droppable` in order to prevent it from collapsing. The placeholder mimics the styling and layout (including `width`, `height`, `margin`, `tagName` and `display`) to ensure the list dimensions remain unaffected while dragging. It will be inserted as a direct sibling to the `React.Node` returned by the `Draggable` children function.

### Adding an `onClick` handler to a `Draggable` or a *drag handle*

You are welcome to add your own `onClick` handler to a `Draggable` or a *drag handle* (which might be the same element). `onClick` events handlers will always be called if a click occurred. If we are preventing the click then we the `event.defaultPrevented` property will be set to `true`. We prevent click events from occurring when the user was dragging an item. See [#sloppy-clicks-and-click-prevention-](sloppy clicks and click prevention) for more information.
Expand Down
21 changes: 9 additions & 12 deletions docs/patterns/multi-drag.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,15 @@ class Task extends Component<Props> {
return (
<Draggable draggableId={task.id} index={this.props.index}>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
<div>
<Container
innerRef={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={this.onClick}
onKeyDown={(event: KeyboardEvent) => this.onKeyDown(event, provided, snapshot)}
>
{task.content}
</Container>
{provided.placeholder}
</div>
<Container
innerRef={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={this.onClick}
onKeyDown={(event: KeyboardEvent) => this.onKeyDown(event, provided, snapshot)}
>
{task.content}
</Container>
)}
</Draggable>
);
Expand Down
66 changes: 66 additions & 0 deletions docs/patterns/tables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Tables

| Benefits of using `<table>` | Provider |
|--------------------------------------------------|-------------------------|
| Clean way of displaying tabular data | Browser |
| Great browser support | Browser |
| Can copy paste the table into other applications | Browser |
| Can reorder items in the table! | `react-beautiful-dnd` 😎|

`react-beautiful-dnd` supports requires no additional wrapping elements to create `Draggable` and `Droppable` components. Therefore it is possible to have a `<table>` that has valid HTML as well as supporting drag and drop.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"react-beautiful-dnd requires no additional"


> We have not found a way to achieve semantic reordering of table columns at this stage. This is because there is no one element that represents a table column - rather, a column is a result of cell placements within repeating rows. As such as cannot wrap a `Draggable` around a 'column' in order to make it draggable. PR's to this guide are welcome if you find a working approach!

## Strategies

There are two strategies you can use when reordering tables.

1. Fixed layouts (faster and simplier)
2. Dimension locking (slower but more robust)

### Strategy 1: fixed layouts

In order to use this strategy the widths of your columns need to be fixed - that is, they will not change depending on what content is placed in the cells. This can be achieve with either a `table-layout: fixed` or `table-layout: auto` as long as you manually set the width of the cells (eg `50%`).

The only thing you need to do is set `display: table` on a `Draggable` row while it is dragging.

[See example code here](TODO)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have code samples for here, here and here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do now! 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the links


### Strategy 2: dimension locking

This strategy will work with columns that have automatic column widths based on content. It will also work with fixed layouts. **It is a more robust strategy than the first, but it is also less performant.**

When we apply `position: fixed` to the dragging item it removes it from the automatic column width calculations that a table uses. So before a drag starts we *lock* all of the cell widths using inline styles to prevent the column dimensions from changing when a drag starts.

This has poor performance characteristics at scale as it requires:

1. Calling `render()` on every row
2. Reading the DOM (`window.getComputedStyles`) on every row

For tables with less than 50 rows this should approach be fine!

[See example code here](TODO)

## Advanced: using [`React.Portal`](https://reactjs.org/docs/portals.html)

If you want to use `React.Portal` in combination with table row reordering then there are few extra steps you need to go through.

First up, have a read of our [using a portal guide](docs/guides/using-a-portal.md) to get familiar with the approach.

It is important to know things timings of mount / unmount actions in React. We have created a [codesandbox.io example](https://codesandbox.io/s/nkl52y1wn0) to show how the mount timings work when moving in and out of a `React.Portal`.

When moving an existing `<tr>` into a `React.Portal` it is important to know that the existing `<tr>` is unmounted and a new `<tr>` is mounted into the portal. Here is the order of those operations:

1. The old `<tr>` has `componentWillUnmount` called
2. The new `<tr>` has `componentWillMount` called

In order to preserve the cell dimensions of the cells in the row that we are moving into a `React.Portal` we need to lock its dimensions using inline styles (see strategy #2). Sadly though, the new component does not directly have access to the information about the component that was in the tree before it moved to the portal. So in order to do this we need to obtain the cell dimensions of the `<tr>` when it is unmounting and re-apply it to the new `<tr>` when it mounted in `componentDidMount`.

There is no great way to do this as when `componentDidMount` is called we are not sure if the component is unmouting as the `tr` is no longer needed, or if it is unmounting because it is about to move into a portal.

It seems like the only way to get things working is to:

1. In `componentWillUnmount` of the `tr` read the current widths of the cells from the DOM. You then store this value outside of the component so that it can be read by new components that are mounting.
2. If a component is mounting and `DraggableStateSnapshot > isDragging` is true then you can see if there is a previously recorded width. If there is then you can apply that width.

This gets a little complicated - so we [created another example](TODO) for you showing you how this technique works! You're welcome!
29 changes: 29 additions & 0 deletions docs/patterns/using-a-portal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Using a Portal

> This guide will go through how you can move your `Draggable` into a [`React.Portal`](https://reactjs.org/docs/portals.html) while dragging.

## Background

We leave elements in place when dragging. We apply `position: fixed` on elements when we are moving them around. This is quite robust and allows for you to have `position: relative | absolute | fixed` parents. However, unfortunately `position:fixed` is [impacted by `transform`](http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/) (such as `transform: rotate(10deg);`). This means that if you have a `transform: *` on one of the parents of a `Draggable` then the positioning logic will be incorrect while dragging. Lame! For most consumers this will not be an issue.

To get around the issue you can use a `portal`.

## `Portals`

Wait, what is a `portal`? A `portal` is a simply another DOM node outside of the current component tree. By using a portal you are able to move the `Draggable` into another DOM node while dragging. This can allow you to get around the limitations of `position: fixed`.

## Not using `React.Portal` by default

React provides a first class api for using `portals`: [`React.Portal`](https://reactjs.org/docs/portals.html). Originally we wanted to use it for all `Draggable`s while dragging. Unfortunately it has a big performance penalty - especially when dragging nodes with a lot of children ([React issue](https://github.com/facebook/react/issues/12247)). The reason for this is because components moving to a `React.Portal` are mounted and remounted which is quite expensive. Therefore we are currently not supporting it out of the box.

If your `Draggable` does not have many children nodes then you are welcome to use `React.Portal` on top of `react-beautiful-dnd`. If you are simply dragging cards in a list then you *should* be fine using `React.Portal`. However, if you are dragging a column full of cards then you will get significant jank when a drag is starting.

## Example

<!-- TODO: embed example here on new website -->

We have created a [working example](https://react-beautiful-dnd.netlify.com/?selectedKind=Portals&selectedStory=Using%20your%20own%20portal&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel) that uses `React.Portal` to guide you. You can view the [source here](https://github.com/atlassian/react-beautiful-dnd/blob/master/stories/11-portal-story.js)

## Tables

If are doing drag and drop reordering within a `<table>` we have created a portal section inside our [table guide](/docs/patterns/tables)
Loading