import type { BoxModel, Rect, Position } from 'css-box-model'; export type Id = string; export type DraggableId = Id; export type DroppableId = Id; export type TypeId = Id; export type ContextId = Id; export type ElementId = Id; export type DroppableMode = 'standard' | 'virtual'; export interface DroppableDescriptor { id: DroppableId; type: TypeId; mode: DroppableMode; } export interface DraggableDescriptor { id: DraggableId; index: number; // Inherited from Droppable droppableId: DroppableId; // This is technically redundant but it avoids // needing to look up a parent droppable just to get its type type: TypeId; } export interface DraggableOptions { canDragInteractiveElements: boolean; shouldRespectForcePress: boolean; isEnabled: boolean; } export type Direction = 'horizontal' | 'vertical'; export interface VerticalAxis { direction: 'vertical'; line: 'y'; start: 'top'; end: 'bottom'; size: 'height'; crossAxisLine: 'x'; crossAxisStart: 'left'; crossAxisEnd: 'right'; crossAxisSize: 'width'; } export interface HorizontalAxis { direction: 'horizontal'; line: 'x'; start: 'left'; end: 'right'; size: 'width'; crossAxisLine: 'y'; crossAxisStart: 'top'; crossAxisEnd: 'bottom'; crossAxisSize: 'height'; } export type Axis = VerticalAxis | HorizontalAxis; export interface ScrollSize { scrollHeight: number; scrollWidth: number; } export interface ScrollDifference { value: Position; // The actual displacement as a result of a scroll is in the opposite // direction to the scroll itself. When scrolling down items are displaced // upwards. This value is the negated version of the 'value' displacement: Position; } export interface ScrollDetails { initial: Position; current: Position; // the maximum allowable scroll for the frame max: Position; diff: ScrollDifference; } export interface Placeholder { client: BoxModel; tagName: string; display: string; } export interface DraggableDimension { descriptor: DraggableDescriptor; // the placeholder for the draggable placeholder: Placeholder; // relative to the viewport when the drag started client: BoxModel; // relative to the whole page page: BoxModel; // how much displacement the draggable causes // this is the size of the marginBox displaceBy: Position; } export interface Scrollable { // This is the window through which the droppable is observed // It does not change during a drag pageMarginBox: Rect; // Used for comparision with dynamic recollecting frameClient: BoxModel; scrollSize: ScrollSize; // Whether or not we should clip the subject by the frame // Is controlled by the ignoreContainerClipping prop shouldClipSubject: boolean; scroll: ScrollDetails; } export interface PlaceholderInSubject { // might not actually be increased by // placeholder if there is no required space increasedBy: Position | null; placeholderSize: Position; // max scroll before placeholder added // will be null if there was no frame oldFrameMaxScroll: Position | null; } export interface DroppableSubject { // raw, unchanging page: BoxModel; withPlaceholder: PlaceholderInSubject | null; // The hitbox for a droppable // - page margin box // - with scroll changes // - with any additional droppable placeholder // - clipped by frame // The subject will be null if the hit area is completely empty active: Rect | null; } export interface DroppableDimension { descriptor: DroppableDescriptor; axis: Axis; isEnabled: boolean; isCombineEnabled: boolean; // relative to the current viewport client: BoxModel; // relative to the whole page isFixedOnPage: boolean; // relative to the page page: BoxModel; // The container of the droppable frame: Scrollable | null; // what is visible through the frame subject: DroppableSubject; } export interface DraggableLocation { droppableId: DroppableId; index: number; } export type DraggableIdMap = { [id in DraggableId]: true; }; export type DroppableIdMap = { [id in DroppableId]: true; }; export type DraggableDimensionMap = { [key in DraggableId]: DraggableDimension; }; export type DroppableDimensionMap = { [key in DroppableId]: DroppableDimension; }; export interface Displacement { draggableId: DraggableId; shouldAnimate: boolean; } export type DisplacementMap = { [key in DraggableId]: Displacement; }; export interface DisplacedBy { value: number; point: Position; } export interface Combine { draggableId: DraggableId; droppableId: DroppableId; } export interface DisplacementGroups { all: DraggableId[]; visible: DisplacementMap; invisible: DraggableIdMap; } export interface ReorderImpact { type: 'REORDER'; destination: DraggableLocation; } export interface CombineImpact { type: 'COMBINE'; combine: Combine; } export type ImpactLocation = ReorderImpact | CombineImpact; export interface Displaced { forwards: DisplacementGroups; backwards: DisplacementGroups; } export interface DragImpact { displaced: DisplacementGroups; displacedBy: DisplacedBy; at: ImpactLocation | null; } export interface ClientPositions { // where the user initially selected // This point is not used to calculate the impact of a dragging item // It is used to calculate the offset from the initial selection point selection: Position; // the current center of the item borderBoxCenter: Position; // how far the item has moved from its original position offset: Position; } export interface PagePositions { selection: Position; borderBoxCenter: Position; // how much the page position has changed from the initial offset: Position; } // There are two seperate modes that a drag can be in // FLUID: everything is done in response to highly granular input (eg mouse) // SNAP: items move in response to commands (eg keyboard); export type MovementMode = 'FLUID' | 'SNAP'; export interface DragPositions { client: ClientPositions; page: PagePositions; } export interface DraggableRubric { draggableId: DraggableId; type: TypeId; source: DraggableLocation; } // Published in onBeforeCapture // We cannot give more information as things might change in the // onBeforeCapture responder! export interface BeforeCapture { draggableId: DraggableId; mode: MovementMode; } // published when a drag starts export interface DragStart extends DraggableRubric { mode: MovementMode; } export interface DragUpdate extends DragStart { // may not have any destination (drag to nowhere) destination: DraggableLocation | null; // populated when a draggable is dragging over another in combine mode combine: Combine | null; } export type DropReason = 'DROP' | 'CANCEL'; // published when a drag finishes export interface DropResult extends DragUpdate { reason: DropReason; } export interface ScrollOptions { shouldPublishImmediately: boolean; } // using the draggable id rather than the descriptor as the descriptor // may change as a result of the initial flush. This means that the lift // descriptor may not be the same as the actual descriptor. To avoid // confusion the request is just an id which is looked up // in the dimension-marshal post-flush // Not including droppableId as it might change in a drop flush export interface LiftRequest { draggableId: DraggableId; scrollOptions: ScrollOptions; } export interface Critical { draggable: DraggableDescriptor; droppable: DroppableDescriptor; } export interface Viewport { // live updates with the latest values frame: Rect; scroll: ScrollDetails; } export interface LiftEffect { inVirtualList: boolean; effected: DraggableIdMap; displacedBy: DisplacedBy; } export interface DimensionMap { draggables: DraggableDimensionMap; droppables: DroppableDimensionMap; } export interface DroppablePublish { droppableId: DroppableId; scroll: Position; } export interface Published { additions: DraggableDimension[]; removals: DraggableId[]; modified: DroppablePublish[]; } export interface CompletedDrag { critical: Critical; result: DropResult; impact: DragImpact; afterCritical: LiftEffect; } export interface IdleState { phase: 'IDLE'; completed: CompletedDrag | null; shouldFlush: boolean; } interface BaseState { phase: unknown; isDragging: true; critical: Critical; movementMode: MovementMode; dimensions: DimensionMap; initial: DragPositions; current: DragPositions; impact: DragImpact; viewport: Viewport; afterCritical: LiftEffect; onLiftImpact: DragImpact; // when there is a fixed list we want to opt out of this behaviour isWindowScrollAllowed: boolean; // if we need to jump the scroll (keyboard dragging) scrollJumpRequest: Position | null; // whether or not draggable movements should be animated forceShouldAnimate: boolean | null; } export interface DraggingState extends BaseState { phase: 'DRAGGING'; } // While dragging we can enter into a bulk collection phase // During this phase no drag updates are permitted. // If a drop occurs during this phase, it must wait until it is // completed before continuing with the drop // TODO: rename to BulkCollectingState export interface CollectingState extends BaseState { phase: 'COLLECTING'; } // If a drop action occurs during a bulk collection we need to // wait for the collection to finish before performing the drop. // This is to ensure that everything has the correct index after // a drop export interface DropPendingState extends BaseState { phase: 'DROP_PENDING'; isWaiting: boolean; reason: DropReason; } // An optional phase for animating the drop / cancel if it is needed export interface DropAnimatingState { phase: 'DROP_ANIMATING'; completed: CompletedDrag; newHomeClientOffset: Position; dropDuration: number; // We still need to render placeholders and fix the dimensions of the dragging item dimensions: DimensionMap; } export type State = | IdleState | DraggingState | CollectingState | DropPendingState | DropAnimatingState; export type StateWhenUpdatesAllowed = DraggingState | CollectingState; export type Announce = (message: string) => void; export type InOutAnimationMode = 'none' | 'open' | 'close'; export interface ResponderProvided { announce: Announce; } export type OnBeforeCaptureResponder = (before: BeforeCapture) => void; export type OnBeforeDragStartResponder = (start: DragStart) => void; export type OnDragStartResponder = ( start: DragStart, provided: ResponderProvided, ) => void; export type OnDragUpdateResponder = ( update: DragUpdate, provided: ResponderProvided, ) => void; export type OnDragEndResponder = ( result: DropResult, provided: ResponderProvided, ) => void; export interface Responders { onBeforeCapture?: OnBeforeCaptureResponder; onBeforeDragStart?: OnBeforeDragStartResponder; onDragStart?: OnDragStartResponder; onDragUpdate?: OnDragUpdateResponder; // always required onDragEnd: OnDragEndResponder; } // Sensors export interface StopDragOptions { shouldBlockNextClick: boolean; } export interface DragActions { drop: (args?: StopDragOptions) => void; cancel: (args?: StopDragOptions) => void; isActive: () => boolean; shouldRespectForcePress: () => boolean; } export interface FluidDragActions extends DragActions { move: (clientSelection: Position) => void; } export interface SnapDragActions extends DragActions { moveUp: () => void; moveDown: () => void; moveRight: () => void; moveLeft: () => void; } export interface PreDragActions { // discover if the lock is still active isActive: () => boolean; // whether it has been indicated if force press should be respected shouldRespectForcePress: () => boolean; // lift the current item fluidLift: (clientSelection: Position) => FluidDragActions; snapLift: () => SnapDragActions; // cancel the pre drag without starting a drag. Releases the lock abort: () => void; } export interface TryGetLockOptions { sourceEvent?: Event; } export type TryGetLock = ( draggableId: DraggableId, forceStop?: () => void, options?: TryGetLockOptions, ) => PreDragActions | null; export interface SensorAPI { tryGetLock: TryGetLock; canGetLock: (id: DraggableId) => boolean; isLockClaimed: () => boolean; tryReleaseLock: () => void; findClosestDraggableId: (event: Event) => DraggableId | null; findOptionsForDraggable: (id: DraggableId) => DraggableOptions | null; } export type Sensor = (api: SensorAPI) => void;