12.0.0
🎧💃 Release song: You can call me Al by Paul Simon
Release at a glance 👀
New
- 👾 Virtual list support: unlocking 10,000 items @ 60fps
- 🎮 Sensor API enabling any input + scripted experiences
- 👶 Can now drag a clone
- 🔒 Strict Content Security Policy (CSP) support and guide
- 🖼 Guide for avoiding image flickering
Improvements
- 🏃♀️ 10% faster + 25% less memory usage for everyone
- 🎯 More robust browser focus retention
- ♿️ Accessibility overhaul with perfect lighthouse score
- 📱 Dragging with touch now allows for reparenting
- 🧨 Reworked error handling
- 🎩 Drop animation now flushed on user scroll
- 👩⚕️ New development only warning for invalid
<Draggable /> | index
setups - 📖 Lots of documentation touch-ups
Will my app break with 12.0
?
For most people: you can safely upgrade to 12.0
! 🎉 If you are doing any of the following then you will need to change some things. This is just a super quick reference to see if you can upgrade safely.
- Patching
onKeyDown
,onMouseDown
oronTouchStart
inDragHandleProps
. These event handlers have been removed to support our new sensor approach, and ultimately for good cloning and virtual list support. - We have renamed our
data-*
attributes. So if you were using them (perhaps in a test), then things will break for you - We are no longer using
aria-roledescription
for lift instructions. Please now use<DragDropContext /> | liftInstruction
- Using
@atlaskit/tree
🌲 or something similar? You will need to stay on11.x
for now
More details on these changes are provided below ↓.
Virtual list support 👾
react-beautiful-dnd
(rbd
) now allows you to:
- reorder items within virtual lists
- move items between virtual lists
Virtual lists offer an extreme performance improvement over standard lists
- Standard lists: performance based on total of list items.
O(n)
- Virtual lists: performance based on (roughly) on amount of visible items. Near
0(1)
(or0(visible n)
)
With react-beautiful-dnd
+ virtual lists it is possible to have drag and drop with 10,000
items running at 60fps 🥳
Here are some examples using react-window
:
It works with keyboard dragging too! 🎹
We have created a virtual list guide as well as react-window
and react-virtualized
examples.
Have a play with it 🤹♂
Library support for virtual lists
react-beautiful-dnd
is designed to work with existing virtual list solutions and does not have its own virtual list abstraction. There is no official "virtual list" specification or implementation for the web. Different virtual list libraries achieve windowing through various techniques. So we cannot guarantee that react-beautiful-dnd
will work with every virtual list library.
Sensor API 🎮
With our Sensor API it is now possible to:
- Create drag and drop interactions from any user input
- Create beautiful scripted experiences
The public Sensor API is the same API that our mouse, keyboard, and touch sensors use. So it is powerful enough to drive any experience we ship out of the box 📦
These are some examples to show off what is possible with the Sensor API. The examples are currently not built to be production-ready. Please reach out if you would like to help improve them or add your own!
Voice 🗣 | Webcam 📷 | Thought 🧠 |
---|---|---|
rbd-voice-sensor created by @danieldelcore |
rbd-webcam-sensor created by @kangweichan |
rbd-thought-sensor created by @charliegerard |
With controls | Run sheet | Onboarding |
---|---|---|
Mapping controls to movements | Running scripted experiences along side a user controlled drag | An scripted onboarding experience for a trip planning application |
✍️ Raathi Kugarajan has written a blog : "Scripted natural motion with react-beautiful-dnd" which outlines how you can create scripted user behaviour with the Sensor API 👏
The Sensor API uses a little state machine model which is pretty cool
Also, sensor
s can be React hooks. Yep! React hooks as public API! 🏄♀️
Cloning API
The Cloning API is a first-class way of reparenting a <Draggable />
s into another DOM location while a drag is occurring. Previously we required you to use your own ReactDOM.createPortal
. We now have a mechanism to do this for you!
When using our cloning API the original <Draggable />
is removed while the drag is being performed; a new clone is rendered (using <Droppable /> | renderClone
) into the container element (controllable using <Droppable /> | getContainerForClone
)
Using our cloning API is required for compatibility with virtual lists.
Strict Content Security Policy (CSP) support 🔒
react-beautiful-dnd
creates some <style>
elements in the <head>
and dynamically updates their values as a performance optimisation (see our preset style guide and Dragging React performance forward). This is considered unsafe inline behaviour and will violate the strict CSP policy: Content-Security-Policy: style-src 'self'
.
A new nonce
mechanism has been created to allow for usage of the stricter style-src 'self'
. A Content Security guide has also been created.
A huge shout out to @Zweder for this addition. He did an enormous amount of work (#1037) including:
- Adding support to add a
nonce
to our<style>
element - Browser testing: Created a stand-alone server with various CSP implementations. Created a
cypress
test to ensure that various CSP configurations behave as expected withrbd
. - Created an initial CSP guide
Thanks @Zweder ❤️👏
Guide: avoiding image flickering
A few people have found that when dragging a <Draggable />
that contains an img
, certain actions can cause the img
to flicker. We have created a new guide that explains what causes image flickering and how you can avoid it.
Better performance 🏃♀️
Even if you are not using virtual lists, things are getting faster!
- ~10% faster dragging
- ~25% less memory usage
Correction: I originally listed non-virtual list gains as: “30% faster dragging and 45% less memory usage”. However, in preparing a blog I did some deeper investigation and these numbers to not be accurate. So I have updated them
These gains came from cleaning up some internal abstractions and from rearchitecting our event system to support virtual lists.
Browser focus management 🕵️♂️
rbd
tries to intelligently retain browser focus on the dragging item through the drag and drop lifecycle if it needs to. The previous approach did not work well in a number of scenarios including combining and our new cloning api.
Internally we have changed to a more robust focus retention solution. We have also created a browser focus guide which goes into detail about the rules that govern when we do and do not give drag handles browser focus.
We have also moved to cypress.io
for our browser focus tests to ensure that our browser focus management is working exactly as we expect.
Setup problem detection and error recovery
#1108. #1472. Thanks for raising this @lakesare and @mattkrick. Thanks @thesuperhomie for exploring the problem!
Historically we have tried hard to ensure that if an error occurs, that the user is given a good experience. However, our error handling strategy had some problems 🤦♂️:
- Multiple logging of the same error
- Subject to infinite loops for setup problems
- Logging of errors not caused by
rbd
- Swallowing of all
invariant
errors, even if they are notrbd
ones
Good news though! All of these problems have been fixed! We also created a new detailed guide which explains exactly what rbd
does when it encounters different types of errors. This makes it easier to know how to do you own error handling in collaboration with rbd
.
Setup problem detection and error recovery 📖
Accessibility 💯
#1288. Thanks @amitnavale for raising this!
Stacy London and Sean Curtis have improved the accessibility of rbd
as a part of an Atlassian shipit project.
rbd
has moved away from usingaria-roledescription
for lift instructions.aria-roledescription
performed inconsistently with various screen reader setups and did not score well with accessibility audit software.rbd
now uses a more robust technique: a visibility hidden element. The text of the element is controlled by<DragDropContext /> | liftInstruction
. This instruction is tied to a drag-handle usingaria-labelledby
. The screen reader guide has been updated.rbd
now has a Google lighthouse build as a part of our CI to ensure thatrbd
has a perfect accessibility score 💯 (at least according to Google)- We have made some minor modifications to the colours in our examples to improve their contrast ratio.
Portals now work with touch dragging
#582. Thanks to @Chariyski, @paceto256, @TrySound
Previously it was not possible to use your own portal (React.createPortal
) with touch dragging (see #582). This was because of how touch events work with DOM element reparenting. The new sensor architecture overcomes this problem. So you are now able to use your own portals with touch dragging! Touch dragging also works perfectly with the new first-class Cloning API
Scrolling clears a drop animation
Any scroll event during a drop animation will now fast forward the drop animation. This prevents an item dropping into a strange location and allows the user to get on with what they are trying to do.
Before | After |
---|---|
If a user scrolls during a drop animation the drop position does not get updated and looks lame | Any user scroll during a drop animation fast forwards the drop |
🐢 Animations slowed down to make what is going on more obvious
Index helper
There are two rules for <Draggable /> | index
values:
- Must be consecutive.
[0, 1, 2]
and not[3, 5, 8]
- Must be unique with a
<Droppable />
(no duplicates). (Technically this one is covered by the first rule, but it is worth calling out)
Not obeying these rules can lead to bugs that can be difficult to track down. In order to help people, we have added development only warning
messages when you violate either of these rules. See Setup problem detection and error recovery
Bug fixes
- If a drag started when a home placeholder was animating closed after a completed drag then the collected dimension could include the size of the collapsing placeholder
- When re-entering a home list that had combine enabled, if you entered below the where the item started then the combine target would be incorrectly calculated #1529. Thanks @kruser for raising this one.
- Auto scrolling on a
position:fixed
list now works correctly in ie11 #1317. Thanks @nathanpower for all your efforts on this one!
Other improvements
- (Internal) New shared registry. This allows for 0(1) lookups across the application
- (Internal) Refactored keyboard displacement engine. In order to support virtual lists, we needed to overhaul how we do keyboard movements. Previously we patched an existing data structure to get the new impact. This patching was kinda gnarly and has been a source of brittleness. Patching also fell over when it came to virtual lists as items potentially displaced items are being added and removed during a drag. We now recalculate the data structure every time based on the
destination
- (Internal) A large movement away from
enzyme
to@testing-library/react
in order to test behaviours rather than implementation. - Unmounting during a drag will still fire the drag end announcement
- Restoring
shouldRespectForcePress
behaviour for touchpads #1401 - Upgrading to
[email protected]
- Upgrading all dependencies
Dropping tree
support (for now) 🌲😢
In order to get virtual list support across the line we needed to remove the previous dynamic addition and removal behaviour which is required for trees. We do plan on supporting this behaviour again soon - we need it for @atlaskit/tree
! For people who are still using this behaviour, you will need to stay on 11.x
for now. You can track updates here: #1547
Adding peer dependency: react-dom
This is technically a breaking change 💥. However, the risk of breaking anyone is extremely low.
Previously we did not have a peerDependency
on react-dom
as we did not call anything from it directly. react-dom
would have been required to mount rbd
into the DOM with ReactDOM.render()
, but rbd
did not use react-dom
directly. Our new Cloning API uses ReactDOM
to create a portal with ReactDOM.createPortal
. So we have added react-dom
as a peerDependency
"peerDependencies": {
"react": "^16.8.5",
+ "react-dom": "^16.8.5"
},
Changes
Updates to our API
data
attributes
We are now leaning on data
attributes a bit more internally. In order to avoid having super long data
attributes we have moved to a new naming scheme for our data
attributes. I will go into the specific changes for our components in following sections
- data-react-beautiful-dnd-*
+ data-rbd-*
Id
s are string
s
Might break people, but we are simply leaning on pre-exiting types
It has always required that draggableId
and a droppableId
be a string
. This is communicated in our type
s. Up until now you technically could, maybe, possibly, sometimes, get away with using a number
when nobody was looking and mum was in the other room. However, due to internal changes that is no longer possible. You will now get errors if a draggableId
or droppableId
is not a string
. We will warn you in the console
about this setup issue. See Setup problem detection and error recovery
DraggableRubric
This is a
minor
change as it introduces a new type
We have created a new type DraggableRubric
which represents core information about a <Draggable />
. It is the same information that was previously available inside of DragStart
just without mode
which changes depending on the drag type. The type has been pulled out of DragStart
for usage elsewhere.
+type DraggableRubric = {|
+ draggableId: DraggableId,
+ type: TypeId,
+ source: DraggableLocation,
+|};
// published when a drag starts
export type DragStart = {|
- draggableId: DraggableId,
- type: TypeId,
- source: DraggableLocation,
+ ...DraggableRubric,
mode: MovementMode,
|};
<Draggable /> | DraggableProvided | DraggableProps
This is a
major
change 💥. The break is caused by a renaming of the data attributes. For most consumers this will be a safe upgrade as these data attributes should generally not be leaned on
The children
function now has a type: DraggableChildrenFn
and a third argument: DraggableRubric
. The rubric
allows you to retrieve information about the <Draggable />
from within your children
function. The new DraggableRubric
value is especially useful when using the new <Droppable /> | renderClone
api. See our new reparenting guide
type DraggableProps = {|
// inline style
style: ?DraggableStyle,
- 'data-react-beautiful-dnd-draggable': string,
+ // used for shared global styles
+ 'data-rbd-draggable-context-id': string,
+ // used for lookups
+ 'data-rbd-draggable-id': string,
// used to know when a transition ends
onTransitionEnd: ?(event: TransitionEvent) => void,
- children: (DraggableProvided, DraggableStateSnapshot) => Node | null+ children: DraggableChildrenFn,
+ // New type for draggable children function.
+ // Adding a third argument to children function: DraggableRubric
+ type DraggableChildrenFn = (
DraggableProvided,
DraggableStateSnapshot,
+ DraggableRubric,
+ ) => Node | null;
|};
<Draggable /> | DraggableProvided | DragHandleProps
This is a
major
change 💥. The most likely this to break people is the removal of the on element event listeners. These are no longer required with our newsensor
approach. So you no longer need to patch event listeners to add your own. We will still callevent.preventDefault()
on events that we use so you know whether they are being used for a drag. See how we use dom events
type DragHandleProps = {|
+ // focus management is now handled without needing these handlers
- onFocus: () => void,
- onBlur: () => void,
+ // new sensor api does not require on element event handlers
- onMouseDown: (event: MouseEvent) => void,
- onKeyDown: (event: KeyboardEvent) => void,
- onTouchStart: (event: TouchEvent) => void,
+ // Moving to new data-* format
- 'data-react-beautiful-dnd-drag-handle': string,
+ // what draggable the handle belongs to
+ 'data-rbd-drag-handle-draggable-id': DraggableId,
+ // What DragDropContext the drag handle is in
+ 'data-rbd-drag-handle-context-id': ContextId,
+ // New way of doing lift announcements
- 'aria-roledescription': string,
+ // id of drag handle aria description for screen readers
+ 'aria-labelledby': ElementId,
tabIndex: number,
draggable: boolean,
onDragStart: (event: DragEvent) => void,
|};
<Droppable />
This is a
minor
change. It enables using a clone as well as virtual lists
type Props = {|
// required
droppableId: DroppableId,
// optional
type?: TypeId,
+ mode?: DroppableMode,
isDropDisabled?: boolean,
isCombineEnabled?: boolean,
direction?: Direction,
ignoreContainerClipping?: boolean,
+ renderClone?: DraggableChildrenFn,
+ getContainerForClone?: () => HTMLElement,
children: (DroppableProvided, DroppableStateSnapshot) => Node,
|};
+ type DroppableMode = 'standard' | 'virtual';
<Droppable /> | DroppableProps
This is a
major
change 💥. For most consumers, this will be safe as these data attributes should generally not be leaned on
type DroppableProps = {|
// used for shared global styles
- 'data-react-beautiful-dnd-droppable': string,
+ 'data-rbd-droppable-context-id': ContextId,
+ // Used to lookup
+ 'data-rbd-droppable-id': DroppableId,
|};
<Droppable /> | DroppableStateSnapshot
This is a
minor
change. It adds some more information that is useful when building virtual lists
type DroppableStateSnapshot = {|
// Is the Droppable being dragged over?
isDraggingOver: boolean,
// What is the id of the draggable that is dragging over the Droppable?
draggingOverWith: ?DraggableId,
// What is the id of the draggable that is dragging from this list?
// Useful for styling the home list when not being dragged over
draggingFromThisWith: ?DraggableId,
+ // Whether or not the placeholder is actively being used.
+ // This is useful information when working with virtual lists
+ isUsingPlaceholder: boolean,
|};
<DragDropContext />
This is a
minor
change. It is goes along with amajor
change 💥 to how we do lift announcements. Lift announcements are now done by the<DragDropContext />
and not<Draggable /> | DraggableProvided | DragHandleProps
type Props = {|
...Responders,
// We do not technically need any children for this component
children: Node | null,
// Read out by screen readers when focusing on a drag handle
+ liftInstruction?: string,
+ // Used for strict content security policies
+ // See our [content security policy guide](/docs/guides/content-security-policy.md)
+ nonce?: string,
+ // See our [sensor api](/docs/sensors/sensor-api.md)
+ sensors?: Sensor[],
+ enableDefaultSensors?: ?boolean,
|};
Thanks 🤝
This release has been a huge effort to get across the line. Thank you to everyone who participated in giving feedback, trying out our alpha
and beta
releases and raising suggestions and bugs. A special thank you to everyone who offered words of encouragement along the way - it helped more than you think.
A big thank you to the following people for their efforts in supporting this release: