Keyboard sensor locks out mouse
If you tab to a draggable element and hit enter to pick it up, and then click inside the DragDropProvider, draggables no longer respond to mouse events. I'm guessing this is the intended behavior: we're already mid-drag, so it shouldn't be possible to initiate another one.
However, from a UI perspective, it just feels like the mouse is locked out. For example, if a user keyboard-drags the element, then forget to keyboard-drop it, they can move to do something else on the page, and when they return to the DragDropProvider, it just seems like it's frozen because they don't realize they never completed the drag.
I've patched around this UI issue by dispatching a fake Tab key event when the user mouses down on the DragDropProvider mid-keyboard-drag:
// If the user tries to click while "dragging" an item with the keyboard, tell the
// KeyboardSensor to drop the item where it is so the PointerSensor can respond.
const onPointerDownCapture = useCallback(() => {
if (dragMethod === DragMethod.KEYBOARD) {
containerRef.current?.dispatchEvent(
new KeyboardEvent("keydown", { bubbles: true, code: "Tab", key: "Tab" }),
);
// Don't preventDefault() or stopPropagation(); users can go straight to dragging
// another item with the pointer if they want.
}
}, [containerRef, dragMethod]);dragMethod comes from a custom drag monitor to track the type of event that activated the drag. I've include the code for it below.
Personally, I think my workaround ought to be the default behavior. But that's your call!
Really, what I'm requesting is (a) a documented way of figuring which sensor is handling the current drag and (b) a way of programmatically telling the KeyboardSensor to complete the drag, rather than having to speak its language by dispatching fake keyboard events.
As promised, here's the code for useDragMethod().
/**
* Infer the drag method from the type of the activating event.
*/
export function inferDragMethod(eventType: string): DragMethod {
if (eventType.startsWith("key")) {
return DragMethod.KEYBOARD;
} else if (eventType.startsWith("pointer")) {
return DragMethod.POINTER;
} else if (eventType.startsWith("touch")) {
return DragMethod.TOUCH;
} else {
return DragMethod.UNKNOWN;
}
}
/**
* Use this hook to track the current DragMethod (or null if no drag is in progress)
*/
export function useDragMethod(): DragMethod | null {
const [method, setMethod] = useState<DragMethod | null>(null);
const onDragStart = useCallback(
(event: DragStartEvent) => setMethod(inferDragMethod(event.nativeEvent?.type ?? "")),
[],
);
const onDragEnd = useCallback(() => setMethod(null), []);
useDragDropMonitor({ onDragStart, onDragEnd });
return method;
}Source: clauderic/dnd-kit