Keyboard sensor locks out mouse
- Dominant language
- TypeScript
- Stars
- 17.6k
- Forks
- 924
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 2
Description
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:
```typescript
// 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()`.
```typescript
/**
* 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(null);
const onDragStart = useCallback(
(event: DragStartEvent) => setMethod(inferDragMethod(event.nativeEvent?.type ?? "")),
[],
);
const onDragEnd = useCallback(() => setMethod(null), []);
useDragDropMonitor({ onDragStart, onDragEnd });
return method;
}
```
Contributor guide
Research direction
Start by reproducing the keyboard-drag-then-pointer interaction in DragDropProvider, then inspect KeyboardSensor, PointerSensor, and the useDragDropMonitor flow described in the issue. Determine how the active sensor is exposed and how keyboard dragging can be completed programmatically. Done means the mouse is no longer effectively locked out and both requested capabilities are documented or supported.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- accessibility, frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100