@hello-pangea/dnd
Version:
Beautiful and accessible drag and drop for lists with React
183 lines (161 loc) • 5.54 kB
text/typescript
import type { Position } from 'css-box-model';
import type {
ReactNode,
DragEventHandler,
TransitionEventHandler,
} from 'react';
import type {
DraggableId,
DroppableId,
DraggableDimension,
State,
MovementMode,
ContextId,
ElementId,
DraggableRubric,
} from '../../types';
import { dropAnimationFinished } from '../../state/action-creators';
export interface DraggingStyle {
position: 'fixed';
top: number;
left: number;
boxSizing: 'border-box';
width: number;
height: number;
transition: string;
transform?: string;
zIndex: number;
// for combining
opacity?: number;
// Avoiding any processing of mouse events.
// This is already applied by the shared styles during a drag.
// During a drop it prevents a draggable from being dragged.
// canStartDrag() will prevent drags in some cases for non primary draggable.
// It is also a minor performance optimisation
pointerEvents: 'none';
}
export interface NotDraggingStyle {
transform?: string;
// null: use the global animation style
// none: skip animation (used in certain displacement situations)
transition?: 'none';
}
export type DraggableStyle = DraggingStyle | NotDraggingStyle;
export interface ZIndexOptions {
dragging: number;
dropAnimating: number;
}
// Props that can be spread onto the element directly
export interface DraggableProvidedDraggableProps {
// inline style
style?: DraggableStyle;
// used for shared global styles
'data-rfd-draggable-context-id': ContextId;
// used for lookups
'data-rfd-draggable-id': DraggableId;
// used to know when a transition ends
onTransitionEnd?: TransitionEventHandler;
}
export interface DraggableProvidedDragHandleProps {
// what draggable the handle belongs to
'data-rfd-drag-handle-draggable-id': DraggableId;
// What DragDropContext the drag handle is in
'data-rfd-drag-handle-context-id': ContextId;
// We need a drag handle to be a widget in order to correctly set accessibility properties
// Note: JAWS and VoiceOver don't need the element to be a 'widget' to read the accessibility properties, but NVDA does
// Using `role="button"` but leaving the public API as a string to allow for changing without a major
role: string;
// Overriding default role to have a more descriptive text ("Draggable item")
// Sadly we cannot use this right now due an issue with lighthouse
// https://github.com/atlassian/react-beautiful-dnd/issues/1742
// 'aria-roledescription': string,
// Using the description property of the drag handle to provide usage instructions
'aria-describedby': ElementId;
// Allow tabbing to this element
// Adding a tab index marks the element as interactive content: https://www.w3.org/TR/html51/dom.html#kinds-of-content-interactive-content
tabIndex: number;
// Opting out of html5 drag and drop
draggable: boolean;
onDragStart: DragEventHandler;
}
export interface DraggableProvided {
draggableProps: DraggableProvidedDraggableProps;
// will be null if the draggable is disabled
dragHandleProps: DraggableProvidedDragHandleProps | null;
// The following props will be removed once we move to react 16
innerRef: (element?: HTMLElement | null) => void;
}
// to easily enable patching of styles
export interface DropAnimation {
duration: number;
curve: string;
moveTo: Position;
opacity: number | null;
scale: number | null;
}
export interface DraggableStateSnapshot {
isDragging: boolean;
isDropAnimating: boolean;
isClone: boolean;
dropAnimation: DropAnimation | null;
draggingOver: DroppableId | null;
// the id of a draggable that you are combining with
combineWith: DraggableId | null;
// a combine target is being dragged over by
combineTargetFor: DraggableId | null;
// What type of movement is being done: 'FLUID' or 'SNAP'
mode: MovementMode | null;
}
export interface DispatchProps {
dropAnimationFinished: typeof dropAnimationFinished;
}
export interface DraggingMapProps {
type: 'DRAGGING';
offset: Position;
mode: MovementMode;
dropping: DropAnimation | null;
draggingOver: DraggableId | null;
combineWith: DraggableId | null;
dimension: DraggableDimension;
forceShouldAnimate: boolean | null;
snapshot: DraggableStateSnapshot;
}
export interface SecondaryMapProps {
type: 'SECONDARY';
offset: Position;
combineTargetFor: DraggableId | null;
shouldAnimateDisplacement: boolean;
snapshot: DraggableStateSnapshot;
}
export type MappedProps = DraggingMapProps | SecondaryMapProps;
export interface MapProps {
// when an item is being displaced by a dragging item,
// we need to know if that movement should be animated
mapped: MappedProps;
// dragging: ?DraggingMapProps,
// secondary: ?SecondaryMapProps,
}
export type DraggableChildrenFn = (
provided: DraggableProvided,
snapshot: DraggableStateSnapshot,
rubic: DraggableRubric,
) => ReactNode | null;
export interface DraggableProps {
draggableId: DraggableId;
index: number;
children: DraggableChildrenFn;
// optional own props
isDragDisabled?: boolean;
disableInteractiveElementBlocking?: boolean;
shouldRespectForcePress?: boolean;
}
export interface PrivateOwnProps extends DraggableProps {
isClone: boolean;
// no longer optional
isEnabled: boolean;
canDragInteractiveElements: boolean;
shouldRespectForcePress: boolean;
}
export type OwnProps = PrivateOwnProps;
export type Props = MapProps & DispatchProps & OwnProps;
export type Selector = (state: State, ownProps: OwnProps) => MapProps;