The modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.
#792 b6970e7
Thanks @clauderic! - The hasSortableData
type-guard that is exported by @dnd-kit/sortable has been updated to also accept the Active
and Over
interfaces so it can be used in events such as onDragStart
, onDragOver
, and onDragEnd
.
Updated dependencies [eaa6e12
]:
eaa6e12
Thanks @clauderic! - Fixed a regression in the KeyboardSensor
scrolling logic.3978c43
Thanks @clauderic! - The ARIA live region element used for screen reader announcements is now positioned using position: fixed
instead of position: absolute
. As of @dnd-kit/core^6.0.0
, the live region element is no longer portaled by default into the document.body
. This change was introduced in order to fix issues with portaled live regions. However, this change can introduce visual regressions when using absolutely positioned elements, since the live region element is constrained to the stacking and position context of its closest positioned ancestor. Using fixed position ensures the element does not introduce visual regressions.e97cb1f
Thanks @clauderic! - The ARIA live region element used for screen reader announcements is now positioned using position: fixed
instead of position: absolute
. As of @dnd-kit/core^6.0.0
, the live region element is no longer portaled by default into the document.body
. This change was introduced in order to fix issues with portaled live regions. However, this change can introduce visual regressions when using absolutely positioned elements, since the live region element is constrained to the stacking and position context of its closest positioned ancestor. Using fixed position ensures the element does not introduce visual regressions.#769 8e3599f
Thanks @clauderic! - Fixed an issue with the containerNodeRect
that is exposed to modifiers having stale properties (top
, left
, etc.) when its scrollable ancestors were scrolled.
#769 53cb962
Thanks @clauderic! - Fixed a regression with scrollable ancestors detection.
The scrollable ancestors should be determined by the active node or the over node exclusively. The draggingNode
variable shouldn't be used to detect scrollable ancestors since it can be the drag overlay node, and the drag overlay node doesn't have any scrollable ancestors because it is a fixed position element.
e5b9d38
Thanks @clauderic! - Fixed a regression with the default drop animation of <DragOverlay>
for consumers using React 18.#746 4173087
Thanks @clauderic! - Accessibility related changes.
Accessibility-related props have been regrouped under the accessibility
prop of <DndContext>
:
<DndContext
- announcements={customAnnouncements}
- screenReaderInstructions={customScreenReaderInstructions}
+ accessibility={{
+ announcements: customAnnouncements,
+ screenReaderInstructions: customScreenReaderInstructions,
+ }}
This is a breaking change that will allow easier addition of new accessibility-related features without overloading the props namespace of <DndContext>
.
The arguments passed to announcement callbacks have changed. They now receive an object that contains the active
and over
properties that match the signature of those passed to the DragEvent handlers (onDragStart
, onDragMove
, etc.). This change allows consumers to read the data
property of the active
and over
node to customize the announcements based on the data.
Example migration steps:
export const announcements: Announcements = {
- onDragStart(id) {
+ onDragStart({active}) {
- return `Picked up draggable item ${id}.`;
+ return `Picked up draggable item ${active.id}.`;
},
- onDragOver(id, overId) {
+ onDragOver({active, over}) {
- if (overId) {
+ if (over) {
- return `Draggable item ${id} was moved over droppable area ${overId}.`;
+ return `Draggable item ${active.id} was moved over droppable area ${over.id}.`;
}
- return `Draggable item ${id} is no longer over a droppable area.`;
+ return `Draggable item ${active.id} is no longer over a droppable area.`;
},
};
The DOM nodes for the screen reader instructions and announcements are no longer portaled into the document.body
element by default.
This change is motivated by the fact that screen readers do not always announce ARIA live regions that are rendered on the document.body
. Common examples of this include when rendering a <DndContext>
within a <dialog>
element or an element that has role="dialog"
, only ARIA live regions rendered within the dialog will be announced.
Consumers can now opt to render announcements in the portal container of their choice using the container
property of the accessibility
prop:
<DndContext
+ accessibility={{
+ container: document.body,
+ }}
#733 035021a
Thanks @clauderic! - The <DragOverlay>
component's drop animation has been refactored, which fixes a number of bugs with the existing implementation and introduces new functionality.
The drop animation now ensures that the the draggable node that we are animating to is in the viewport before performing the drop animation and scrolls it into view if needed.
dropAnimation
propThe dropAnimation
prop of <DragOverlay>
now accepts either a configuration object or a custom drop animation function.
The configuration object adheres to the following shape:
interface DropAnimationOptions {
duration?: number;
easing?: string;
keyframes?: DropAnimationKeyframeResolver;
sideEffects?: DropAnimationSideEffects;
}
The default drop animation options are:
const defaultDropAnimationConfiguration: DropAnimationOptions = {
duration: 250,
easing: 'ease',
keyframes: defaultDropAnimationKeyframes,
sideEffects: defaultDropAnimationSideEffects({
styles: {
active: {
opacity: '0',
},
},
}),
};
The keyframes
option allows consumers to override the keyframes of the drop animation. For example, here is how you would add a fade out transition to the drop animation using keyframes:
import {CSS} from '@dnd-kit/utilities';
const customDropAnimation = {
keyframes({transform}) {
return [
{opacity: 1, transform: CSS.Transform.toString(transform.initial)},
{opacity: 0, transform: CSS.Transform.toString(transform.final)},
];
},
};
The dragSourceOpacity
option has been deprecated in favour of letting consumers define arbitrary side effects that should run before the animation starts. Side effects may return a cleanup function that should run when the drop animation has completed.
type CleanupFunction = () => void;
export type DropAnimationSideEffects = (
parameters: DropAnimationSideEffectsParameters
) => CleanupFunction | void;
Drop animation side effects are a powerful abstraction that provide a lot of flexibility. The defaultDropAnimationSideEffects
function is exported by @dnd-kit/core
and aims to facilitate the types of side-effects we anticipate most consumers will want to use out of the box:
interface DefaultDropAnimationSideEffectsOptions {
// Apply a className on the active draggable or drag overlay node during the drop animation
className?: {
active?: string;
dragOverlay?: string;
};
// Apply temporary styles to the active draggable node or drag overlay during the drop animation
styles?: {
active?: Styles;
dragOverlay?: Styles;
};
}
For advanced side-effects, consumers may define a custom sideEffects
function that may optionally return a cleanup function that will be executed when the drop animation completes:
const customDropAnimation = {
sideEffects({active}) {
active.node.classList.add('dropAnimationInProgress');
active.node.animate([{opacity: 0}, {opacity: 1}], {
easing: 'ease-in',
duration: 250,
});
return () => {
// Clean up when the drop animation is complete
active.node.classList.remove('dropAnimationInProgress');
};
},
};
For even more advanced use-cases, consumers may also provide a function to the dropAnimation
prop, which adheres to the following shape:
interface DropAnimationFunctionArguments {
active: {
id: UniqueIdentifier;
data: DataRef;
node: HTMLElement;
rect: ClientRect;
};
draggableNodes: DraggableNodes;
dragOverlay: {
node: HTMLElement;
rect: ClientRect;
};
droppableContainers: DroppableContainers;
measuringConfiguration: DeepRequired<MeasuringConfiguration>;
transform: Transform;
}
type DropAnimationFunction = (
args: DropAnimationFunctionArguments
) => Promise<void> | void;
<DragOverlay>
now respects the measuringConfiguration
specified for the dragOverlay
and draggable
properties when measuring the rects to animate to and from.<DragOverlay>
component now supports rendering children while performing the drop animation. Previously, the drag overlay would be in a broken state when trying to pick up an item while a drop animation was in progress.For consumers that were relying on the dragSourceOpacity
property in their dropAnimation
configuration:
+ import {defaultDropAnimationSideEffects} from '@dnd-kit/core';
const dropAnimation = {
- dragSourceOpacity: 0.5,
+ sideEffects: defaultDropAnimationSideEffects({
+ styles : {
+ active: {
+ opacity: '0.5',
+ },
+ },
+ ),
};
#745 5f3c700
Thanks @clauderic! - The keyboard sensor now keeps track of the initial coordinates of the collision rect to provide a translate delta when move events are dispatched.
This is a breaking change that may affect consumers that had created custom keyboard coordinate getters.
Previously the keyboard sensor would measure the initial rect of the active node and store its top and left properties as its initial coordinates it would then compare all subsequent move coordinates to calculate the delta.
This approach suffered from the following issues:
<DndContext>
for the draggable nodecurrentCoordinates
passed to the coordinate getter were often stale and not an accurate representation of the current position of the collision rect, which can be affected by a number of different variables, such as modifiers.#755 33e6dd2
Thanks @clauderic! - The UniqueIdentifier
type has been updated to now accept either string
or number
identifiers. As a result, the id
property of useDraggable
, useDroppable
and useSortable
and the items
prop of <SortableContext>
now all accept either string
or number
identifiers.
For consumers that are using TypeScript, import the UniqueIdentifier
type to have strongly typed local state:
+ import type {UniqueIdentifier} from '@dnd-kit/core';
function MyComponent() {
- const [items, setItems] = useState(['A', 'B', 'C']);
+ const [items, setItems] = useState<UniqueIdentifier>(['A', 'B', 'C']);
}
Alternatively, consumers can cast or convert the id
property to a string
when reading the id
property of interfaces such as Active
, Over
, DroppableContainer
and DraggableNode
.
The draggableNodes
object has also been converted to a map. Consumers that were reading from the draggableNodes
property that is available on the public context of <DndContext>
should follow these migration steps:
- draggableNodes[someId];
+ draggableNodes.get(someId);
#748 59ca82b
Thanks @clauderic! - Automatic focus management and activator node refs.
Introducing the concept of activator node refs for useDraggable
and useSortable
. This allows @dnd-kit to handle common use-cases such as restoring focus on the activator node after dragging via the keyboard or only allowing the activator node to instantiate the keyboard sensor.
Consumers of useDraggable
and useSortable
may now optionally set the activator node ref on the element that receives listeners:
import {useDraggable} from '@dnd-kit/core';
function Draggable(props) {
const {
listeners,
setNodeRef,
+ setActivatorNodeRef,
} = useDraggable({id: props.id});
return (
<div ref={setNodeRef}>
Draggable element
<button
{...listeners}
+ ref={setActivatorNodeRef}
>
:: Drag Handle
</button>
</div>
)
}
It's common for the activator element (the element that receives the sensor listeners) to differ from the draggable node. When this happens, @dnd-kit has no reliable way to get a reference to the activator node after dragging ends, as the original event.target
that instantiated the sensor may no longer be mounted in the DOM or associated with the draggable node that was previously active.
Focus management is now automatically handled by @dnd-kit. When the activator event is a Keyboard event, @dnd-kit will now attempt to automatically restore focus back to the first focusable node of the activator node or draggable node.
If no activator node is specified via the setActivatorNodeRef
setter function of useDraggble
and useSortable
, @dnd-kit will automatically restore focus on the first focusable node of the draggable node set via the setNodeRef
setter function of useDraggable
and useSortable
.
If you were previously managing focus manually and would like to opt-out of automatic focus management, use the newly introduced restoreFocus
property of the accessibility
prop of <DndContext>
:
<DndContext
accessibility={{
+ restoreFocus: false
}}
#751 a52fba1
Thanks @clauderic! - Added the aria-disabled
attribute to the attribtues
object returned by useDraggable
and useSortable
. The value of the aria-disabled
attribute is populated based on whether or not the disabled
argument is passed to useDraggble
or useSortable
.
#741 40707ce
Thanks @clauderic! - The auto scroller now keeps track of the drag direction to infer scroll intent. By default, auto-scrolling will now be disabled for a given direction if dragging in that direction hasn't occurred yet. This prevents accidental auto-scrolling when picking up a draggable item that is near the scroll boundary threshold.
#660 a41e5b8
Thanks @clauderic! - Fixed a bug with the delta
property returned in onDragMove
, onDragOver
, onDragEnd
and onDragCancel
. The delta
property represents the transform
delta since dragging was initiated, along with the scroll delta. However, due to an oversight, the delta
property was actually returning the transform
delta and the current scroll offsets rather than the scroll delta.
This same change has been made to the scrollAdjustedTranslate
property that is exposed to sensors.
#750 bf30718
Thanks @clauderic! - The useDndMonitor()
hook has been refactored to be synchronously invoked at the same time as the events dispatched by <DndContext>
(such as onDragStart
, onDragOver
, onDragEnd
).
The new refactor uses the subscribe/notify pattern and no longer causes re-renders in consuming components of useDndMonitor()
when events are dispatched.
#660 a41e5b8
Thanks @clauderic! - The activeNodeRect
and containerNodeRect
are now observed by a ResizeObserver
in case they resize while dragging.
#660 a41e5b8
Thanks @clauderic! - Improved useDraggable
usage without <DragOverlay>
:
<DragOverlay>
used.#660 77e3d44
Thanks @clauderic! - Fixed an issue with useDroppable
hook needlessly dispatching SetDroppableDisabled
actions even if the disabled
property had not changed since registering the droppable.
#749 188a450
Thanks @clauderic! - The onDragStart
, onDragMove
, onDragOver
, onDragEnd
and onDragCancel
events of <DndContext>
and useDndMonitor
now expose the activatorEvent
event that instantiated the activated sensor.
#733 035021a
Thanks @clauderic! - The KeyboardSensor
now scrolls the focused activator draggable node into view if it is not within the viewport.
#733 035021a
Thanks @clauderic! - By default, @dnd-kit now attempts to compensate for layout shifts that happen right after the onDragStart
event is dispatched by scrolling the first scrollable ancestor of the active draggable node.
The autoScroll
prop of <DndContext>
now optionally accepts a layoutShiftCompensation
property to control this new behavior:
interface AutoScrollOptions {
acceleration?: number;
activator?: AutoScrollActivator;
canScroll?: CanScroll;
enabled?: boolean;
interval?: number;
+ layoutShiftCompensation?: boolean | {x: boolean, y: boolean};
order?: TraversalOrder;
threshold?: {
x: number;
y: number;
};
}
To enable/disable layout shift scroll compensation for a single scroll axis, pass in the following autoscroll configuration to <DndContext>
:
<DndContext
autoScroll={{layoutShiftCompensation: {x: false, y: true}}}
>
To completely disable layout shift scroll compensation, pass in the following autoscroll configuration to <DndContext>
:
<DndContext
autoScroll={{layoutShiftCompensation: false}}
>
#672 10f6836
Thanks @clauderic! - The measureDroppableContainers
method now properly respects the MeasuringStrategy defined on <DndContext />
and will not measure containers while measuring is disabled.
#656 c1b3b5a
Thanks @clauderic! - Fixed an issue with collision detection using stale rects. The droppableRects
property has been added to the CollisionDetection
interface.
All built-in collision detection algorithms have been updated to get the rect for a given droppable container from droppableRects
rather than from the rect.current
ref:
- const rect = droppableContainers.get(id).rect.current;
+ const rect = droppableRects.get(id);
The rect.current
ref stored on DroppableContainers can be stale if measuring is scheduled but has not completed yet. Collision detection algorithms should use the droppableRects
map instead to get the latest, most up-to-date measurement of a droppable container in order to avoid computing collisions against stale rects.
This is not a breaking change. However, if you've forked any of the built-in collision detection algorithms or you've authored custom ones, we highly recommend you update your use-cases to avoid possibly computing collisions against stale rects.
#742 7161f70
Thanks @clauderic! - Fallback to initial rect measured for the active draggable node if it unmounts during initialization (after onDragStart
is dispatched).
#749 5811986
Thanks @clauderic! - The Data
and DataRef
types are now exported by @dnd-kit/core
.
#699 e302bd4
Thanks @JuAn-Kang! - Export DragOverlayProps
for consumers.
750d726
Thanks @clauderic! - Fixed a bug in the KeyboardSensor
where it would not move the draggable on the horizontal axis if it could fully scroll to the new vertical coordinates, and would not move the draggable on the vertical axis if it could fully scroll to the new horizontal coordinates.
#660 e6e242c
Thanks @clauderic! - The KeyboardSensor
was updated to use scrollTo
instead of scrollBy
when it is able to fully scroll to the new coordinates returned by the coordinate getter function. This resolves issues that can happen with scrollBy
when called in rapid succession.
Updated dependencies [59ca82b
, 035021a
]:
#748 59ca82b
Thanks @clauderic! - Introduced the findFirstFocusableNode
utility function that returns the first focusable node within a given HTMLElement, or the element itself if it is focusable.
#733 035021a
Thanks @clauderic! - Introduced the useEvent
hook based on implementation breakdown in the RFC. In the future, this hook will be used as a polyfill if the native React hook is unavailble.