@react-aria/dnd
Advanced tools
Comparing version
import React, { HTMLAttributes, RefObject, Key } from "react"; | ||
import { DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropMoveEvent, DropOperation, DragTypes, DroppableCollectionProps, DropTarget, KeyboardDelegate, DragEndEvent, DragItem, DragMoveEvent, DragPreviewRenderer, DragStartEvent, DOMAttributes, DropItem } from "@react-types/shared"; | ||
import { DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropMoveEvent, DropOperation, DragTypes, DroppableCollectionProps, DropTargetDelegate, KeyboardDelegate, DropTarget, DragEndEvent, DragItem, DragMoveEvent, DragPreviewRenderer, DragStartEvent, DOMAttributes, DropItem, Collection, Node } from "@react-types/shared"; | ||
import { DroppableCollectionState, DraggableCollectionState } from "@react-stately/dnd"; | ||
@@ -7,8 +7,20 @@ import { AriaButtonProps } from "@react-types/button"; | ||
ref: RefObject<HTMLElement>; | ||
/** | ||
* A function returning the drop operation to be performed when items matching the given types are dropped | ||
* on the drop target. | ||
*/ | ||
getDropOperation?: (types: DragTypes, allowedOperations: DropOperation[]) => DropOperation; | ||
getDropOperationForPoint?: (types: DragTypes, allowedOperations: DropOperation[], x: number, y: number) => DropOperation; | ||
/** Handler that is called when a valid drag enters the drop target. */ | ||
onDropEnter?: (e: DropEnterEvent) => void; | ||
/** Handler that is called when a valid drag is moved within the drop target. */ | ||
onDropMove?: (e: DropMoveEvent) => void; | ||
/** | ||
* Handler that is called after a valid drag is held over the drop target for a period of time. | ||
* This typically opens the item so that the user can drop within it. | ||
*/ | ||
onDropActivate?: (e: DropActivateEvent) => void; | ||
/** Handler that is called when a valid drag exits the drop target. */ | ||
onDropExit?: (e: DropExitEvent) => void; | ||
/** Handler that is called when a valid drag is dropped on the drop target. */ | ||
onDrop?: (e: DropEvent) => void; | ||
@@ -23,3 +35,3 @@ } | ||
keyboardDelegate: KeyboardDelegate; | ||
getDropTargetFromPoint: (x: number, y: number) => DropTarget | null; | ||
dropTargetDelegate: DropTargetDelegate; | ||
} | ||
@@ -35,2 +47,3 @@ export interface DroppableCollectionResult { | ||
dropProps: HTMLAttributes<HTMLElement>; | ||
isDropTarget: boolean; | ||
} | ||
@@ -43,2 +56,4 @@ export function useDroppableItem(options: DroppableItemOptions, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DroppableItemResult; | ||
dropIndicatorProps: HTMLAttributes<HTMLElement>; | ||
isDropTarget: boolean; | ||
isHidden: boolean; | ||
} | ||
@@ -82,3 +97,8 @@ export function useDropIndicator(props: DropIndicatorProps, state: DroppableCollectionState, ref: RefObject<HTMLElement>): DropIndicatorAria; | ||
export function useClipboard(options: ClipboardProps): ClipboardResult; | ||
export class ListDropTargetDelegate implements DropTargetDelegate { | ||
constructor(collection: Collection<Node<unknown>>, ref: RefObject<HTMLElement>); | ||
getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget; | ||
} | ||
export type { DropTargetDelegate } from '@react-types/shared'; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@react-aria/dnd", | ||
"version": "3.0.0-nightly.3408+43ef6aeaa", | ||
"version": "3.0.0-nightly.3412+93d8d401a", | ||
"description": "Spectrum UI components in React", | ||
@@ -21,13 +21,13 @@ "license": "Apache-2.0", | ||
"@babel/runtime": "^7.6.2", | ||
"@internationalized/string": "3.0.1-nightly.3408+43ef6aeaa", | ||
"@react-aria/i18n": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-aria/interactions": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-aria/live-announcer": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-aria/overlays": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-aria/utils": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-aria/visually-hidden": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-stately/dnd": "3.0.0-nightly.3408+43ef6aeaa", | ||
"@react-stately/selection": "3.0.0-nightly.1708+43ef6aeaa", | ||
"@react-types/button": "3.6.1-nightly.3408+43ef6aeaa", | ||
"@react-types/shared": "3.0.0-nightly.1708+43ef6aeaa" | ||
"@internationalized/string": "3.0.1-nightly.3412+93d8d401a", | ||
"@react-aria/i18n": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-aria/interactions": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-aria/live-announcer": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-aria/overlays": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-aria/utils": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-aria/visually-hidden": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-stately/dnd": "3.0.0-nightly.3412+93d8d401a", | ||
"@react-stately/selection": "3.0.0-nightly.1712+93d8d401a", | ||
"@react-types/button": "3.6.1-nightly.3412+93d8d401a", | ||
"@react-types/shared": "3.0.0-nightly.1712+93d8d401a" | ||
}, | ||
@@ -41,3 +41,3 @@ "peerDependencies": { | ||
}, | ||
"gitHead": "43ef6aeaadae709d2b704527deabde186814a2be" | ||
"gitHead": "93d8d401ab8f428fc6fd6f172023282ed1a41341" | ||
} |
@@ -78,3 +78,4 @@ /* | ||
) { | ||
dragSession.next(); | ||
let target = dragSession.findNearestDropTarget(); | ||
dragSession.setCurrentDropTarget(target); | ||
} | ||
@@ -415,2 +416,21 @@ }); | ||
findNearestDropTarget(): DropTarget { | ||
let dragTargetRect = this.dragTarget.element.getBoundingClientRect(); | ||
let minDistance = Infinity; | ||
let nearest = null; | ||
for (let dropTarget of this.validDropTargets) { | ||
let rect = dropTarget.element.getBoundingClientRect(); | ||
let dx = rect.left - dragTargetRect.left; | ||
let dy = rect.top - dragTargetRect.top; | ||
let dist = (dx * dx) + (dy * dy); | ||
if (dist < minDistance) { | ||
minDistance = dist; | ||
nearest = dropTarget; | ||
} | ||
} | ||
return nearest; | ||
} | ||
setCurrentDropTarget(dropTarget: DropTarget, item?: DroppableItem) { | ||
@@ -417,0 +437,0 @@ if (dropTarget !== this.currentDropTarget) { |
@@ -21,2 +21,3 @@ /* | ||
export type {ClipboardProps, ClipboardResult} from './useClipboard'; | ||
export type {DropTargetDelegate} from '@react-types/shared'; | ||
@@ -31,1 +32,2 @@ export {useDrag} from './useDrag'; | ||
export {DragPreview} from './DragPreview'; | ||
export {ListDropTargetDelegate} from './ListDropTargetDelegate'; |
@@ -23,10 +23,20 @@ /* | ||
ref: RefObject<HTMLElement>, | ||
/** | ||
* A function returning the drop operation to be performed when items matching the given types are dropped | ||
* on the drop target. | ||
*/ | ||
getDropOperation?: (types: IDragTypes, allowedOperations: DropOperation[]) => DropOperation, | ||
getDropOperationForPoint?: (types: IDragTypes, allowedOperations: DropOperation[], x: number, y: number) => DropOperation, | ||
/** Handler that is called when a valid drag enters the drop target. */ | ||
onDropEnter?: (e: DropEnterEvent) => void, | ||
/** Handler that is called when a valid drag is moved within the drop target. */ | ||
onDropMove?: (e: DropMoveEvent) => void, | ||
// When the user hovers over the drop target for a period of time. | ||
// typically opens that item. macOS/iOS call this "spring loading". | ||
/** | ||
* Handler that is called after a valid drag is held over the drop target for a period of time. | ||
* This typically opens the item so that the user can drop within it. | ||
*/ | ||
onDropActivate?: (e: DropActivateEvent) => void, | ||
/** Handler that is called when a valid drag exits the drop target. */ | ||
onDropExit?: (e: DropExitEvent) => void, | ||
/** Handler that is called when a valid drag is dropped on the drop target. */ | ||
onDrop?: (e: DropEvent) => void | ||
@@ -47,7 +57,34 @@ } | ||
y: 0, | ||
dragEnterCount: 0, | ||
dragOverElements: new Set<Element>(), | ||
dropEffect: 'none' as DataTransfer['dropEffect'], | ||
effectAllowed: 'none' as DataTransfer['effectAllowed'], | ||
dropActivateTimer: null | ||
}).current; | ||
let fireDropEnter = (e: DragEvent) => { | ||
setDropTarget(true); | ||
if (typeof options.onDropEnter === 'function') { | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
options.onDropEnter({ | ||
type: 'dropenter', | ||
x: e.clientX - rect.x, | ||
y: e.clientY - rect.y | ||
}); | ||
} | ||
}; | ||
let fireDropExit = (e: DragEvent) => { | ||
setDropTarget(false); | ||
if (typeof options.onDropExit === 'function') { | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
options.onDropExit({ | ||
type: 'dropexit', | ||
x: e.clientX - rect.x, | ||
y: e.clientY - rect.y | ||
}); | ||
} | ||
}; | ||
let onDragOver = (e: DragEvent) => { | ||
@@ -57,3 +94,3 @@ e.preventDefault(); | ||
if (e.clientX === state.x && e.clientY === state.y) { | ||
if (e.clientX === state.x && e.clientY === state.y && e.dataTransfer.effectAllowed === state.effectAllowed) { | ||
e.dataTransfer.dropEffect = state.dropEffect; | ||
@@ -66,2 +103,16 @@ return; | ||
let prevDropEffect = state.dropEffect; | ||
// Update drop effect if allowed drop operations changed (e.g. user pressed modifier key). | ||
if (e.dataTransfer.effectAllowed !== state.effectAllowed) { | ||
let allowedOperations = effectAllowedToOperations(e.dataTransfer.effectAllowed); | ||
let dropOperation = allowedOperations[0]; | ||
if (typeof options.getDropOperation === 'function') { | ||
let types = new DragTypes(e.dataTransfer); | ||
dropOperation = getDropOperation(e.dataTransfer.effectAllowed, options.getDropOperation(types, allowedOperations)); | ||
} | ||
state.dropEffect = DROP_OPERATION_TO_DROP_EFFECT[dropOperation] || 'none'; | ||
} | ||
if (typeof options.getDropOperationForPoint === 'function') { | ||
@@ -71,9 +122,20 @@ let allowedOperations = effectAllowedToOperations(e.dataTransfer.effectAllowed); | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
let dropOperation = options.getDropOperationForPoint(types, allowedOperations, state.x - rect.x, state.y - rect.y); | ||
let dropOperation = getDropOperation( | ||
e.dataTransfer.effectAllowed, | ||
options.getDropOperationForPoint(types, allowedOperations, state.x - rect.x, state.y - rect.y) | ||
); | ||
state.dropEffect = DROP_OPERATION_TO_DROP_EFFECT[dropOperation] || 'none'; | ||
} | ||
state.effectAllowed = e.dataTransfer.effectAllowed; | ||
e.dataTransfer.dropEffect = state.dropEffect; | ||
if (typeof options.onDropMove === 'function') { | ||
// If the drop operation changes, update state and fire events appropriately. | ||
if (state.dropEffect === 'none' && prevDropEffect !== 'none') { | ||
fireDropExit(e); | ||
} else if (state.dropEffect !== 'none' && prevDropEffect === 'none') { | ||
fireDropEnter(e); | ||
} | ||
if (typeof options.onDropMove === 'function' && state.dropEffect !== 'none') { | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
@@ -103,4 +165,4 @@ options.onDropMove({ | ||
e.stopPropagation(); | ||
state.dragEnterCount++; | ||
if (state.dragEnterCount > 1) { | ||
state.dragOverElements.add(e.target as Element); | ||
if (state.dragOverElements.size > 1) { | ||
return; | ||
@@ -114,29 +176,23 @@ } | ||
let types = new DragTypes(e.dataTransfer); | ||
dropOperation = options.getDropOperation(types, allowedOperations); | ||
dropOperation = getDropOperation(e.dataTransfer.effectAllowed, options.getDropOperation(types, allowedOperations)); | ||
} | ||
if (dropOperation !== 'cancel') { | ||
setDropTarget(true); | ||
} | ||
if (typeof options.getDropOperationForPoint === 'function') { | ||
let types = new DragTypes(e.dataTransfer); | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
dropOperation = options.getDropOperationForPoint(types, allowedOperations, e.clientX - rect.x, e.clientY - rect.y); | ||
dropOperation = getDropOperation( | ||
e.dataTransfer.effectAllowed, | ||
options.getDropOperationForPoint(types, allowedOperations, e.clientX - rect.x, e.clientY - rect.y) | ||
); | ||
} | ||
state.x = e.clientX; | ||
state.y = e.clientY; | ||
state.effectAllowed = e.dataTransfer.effectAllowed; | ||
state.dropEffect = DROP_OPERATION_TO_DROP_EFFECT[dropOperation] || 'none'; | ||
e.dataTransfer.dropEffect = state.dropEffect; | ||
if (typeof options.onDropEnter === 'function' && dropOperation !== 'cancel') { | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
options.onDropEnter({ | ||
type: 'dropenter', | ||
x: e.clientX - rect.x, | ||
y: e.clientY - rect.y | ||
}); | ||
if (dropOperation !== 'cancel') { | ||
fireDropEnter(e); | ||
} | ||
state.x = e.clientX; | ||
state.y = e.clientY; | ||
}; | ||
@@ -146,17 +202,26 @@ | ||
e.stopPropagation(); | ||
state.dragEnterCount--; | ||
if (state.dragEnterCount > 0) { | ||
// We would use e.relatedTarget to detect if the drag is still inside the drop target, | ||
// but it is always null in WebKit. https://bugs.webkit.org/show_bug.cgi?id=66547 | ||
// Instead, we track all of the targets of dragenter events in a set, and remove them | ||
// in dragleave. When the set becomes empty, we've left the drop target completely. | ||
// We must also remove any elements that are no longer in the DOM, because dragleave | ||
// events will never be fired for these. This can happen, for example, with drop | ||
// indicators between items, which disappear when the drop target changes. | ||
state.dragOverElements.delete(e.target as Element); | ||
for (let element of state.dragOverElements) { | ||
if (!e.currentTarget.contains(element)) { | ||
state.dragOverElements.delete(element); | ||
} | ||
} | ||
if (state.dragOverElements.size > 0) { | ||
return; | ||
} | ||
if (typeof options.onDropExit === 'function' && state.dropEffect !== 'none') { | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
options.onDropExit({ | ||
type: 'dropexit', | ||
x: e.clientX - rect.x, | ||
y: e.clientY - rect.y | ||
}); | ||
if (state.dropEffect !== 'none') { | ||
fireDropExit(e); | ||
} | ||
setDropTarget(false); | ||
clearTimeout(state.dropActivateTimer); | ||
@@ -191,13 +256,4 @@ }; | ||
if (typeof options.onDropExit === 'function') { | ||
let rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); | ||
options.onDropExit({ | ||
type: 'dropexit', | ||
x: e.clientX - rect.x, | ||
y: e.clientY - rect.y | ||
}); | ||
} | ||
state.dragEnterCount = 0; | ||
setDropTarget(false); | ||
state.dragOverElements.clear(); | ||
fireDropExit(e); | ||
clearTimeout(state.dropActivateTimer); | ||
@@ -267,1 +323,7 @@ }; | ||
} | ||
function getDropOperation(effectAllowed: string, operation: DropOperation) { | ||
let allowedOperationsBits = DROP_OPERATION_ALLOWED[effectAllowed]; | ||
let op = DROP_OPERATION[operation]; | ||
return allowedOperationsBits & op ? operation : 'cancel'; | ||
} |
@@ -29,3 +29,5 @@ /* | ||
export interface DropIndicatorAria { | ||
dropIndicatorProps: HTMLAttributes<HTMLElement> | ||
dropIndicatorProps: HTMLAttributes<HTMLElement>, | ||
isDropTarget: boolean, | ||
isHidden: boolean | ||
} | ||
@@ -76,2 +78,4 @@ | ||
let isDropTarget = state.isDropTarget(target); | ||
let ariaHidden = !dragSession ? 'true' : dropProps['aria-hidden']; | ||
return { | ||
@@ -84,6 +88,11 @@ dropIndicatorProps: { | ||
'aria-labelledby': labelledBy, | ||
'aria-hidden': !dragSession ? 'true' : dropProps['aria-hidden'], | ||
'aria-hidden': ariaHidden, | ||
tabIndex: -1 | ||
} | ||
}, | ||
isDropTarget, | ||
// If aria-hidden, we are either not in a drag session or the drop target is invalid. | ||
// In that case, there's no need to render anything at all unless we need to show the indicator visually. | ||
// This can happen when dragging using the native DnD API as opposed to keyboard dragging. | ||
isHidden: !isDropTarget && !!ariaHidden | ||
}; | ||
} |
@@ -13,3 +13,3 @@ /* | ||
import {Collection, DropEvent, DropOperation, DroppableCollectionProps, DropPosition, DropTarget, KeyboardDelegate, Node} from '@react-types/shared'; | ||
import {Collection, DropEvent, DropOperation, DroppableCollectionProps, DropPosition, DropTarget, DropTargetDelegate, KeyboardDelegate, Node} from '@react-types/shared'; | ||
import * as DragManager from './DragManager'; | ||
@@ -27,3 +27,3 @@ import {DroppableCollectionState} from '@react-stately/dnd'; | ||
keyboardDelegate: KeyboardDelegate, | ||
getDropTargetFromPoint: (x: number, y: number) => DropTarget | null | ||
dropTargetDelegate: DropTargetDelegate | ||
} | ||
@@ -57,5 +57,4 @@ | ||
ref, | ||
onDropEnter(e) { | ||
let target = props.getDropTargetFromPoint(e.x, e.y); | ||
state.setTarget(target); | ||
onDropEnter() { | ||
state.setTarget(localState.nextTarget); | ||
}, | ||
@@ -67,3 +66,4 @@ onDropMove(e) { | ||
getDropOperationForPoint(types, allowedOperations, x, y) { | ||
let target = props.getDropTargetFromPoint(x, y); | ||
let isValidDropTarget = (target) => state.getDropOperation(target, types, allowedOperations) !== 'cancel'; | ||
let target = props.dropTargetDelegate.getDropTargetFromPoint(x, y, isValidDropTarget); | ||
if (!target) { | ||
@@ -70,0 +70,0 @@ localState.dropOperation = 'cancel'; |
@@ -25,3 +25,4 @@ /* | ||
export interface DroppableItemResult { | ||
dropProps: HTMLAttributes<HTMLElement> | ||
dropProps: HTMLAttributes<HTMLElement>, | ||
isDropTarget: boolean | ||
} | ||
@@ -66,4 +67,5 @@ | ||
'aria-hidden': !dragSession || isValidDropTarget ? undefined : 'true' | ||
} | ||
}, | ||
isDropTarget | ||
}; | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
948021
5.26%24
4.35%8311
5.58%Updated
Updated
Updated
Updated
Updated