clauderic / dnd-kit

The modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.
http://dndkit.com
MIT License
11.9k stars 602 forks source link

Cannot auto-scroll back to first vertical list when used in a horizontally scrollable parent #1425

Open chadlavi opened 3 weeks ago

chadlavi commented 3 weeks ago

I'm encountering a strange problem with a basic dnd-kit/core setup.

I have Trello-like vertical columns that contain droppables. the columns are in a horizontally scrollable parent.

If there are enough columns that they do not all fit in the viewport at once, then autoscroll will mean that any draggable that is dragged from the leftmost column cannot be put back into the leftmost column once it has scrolled off the left edge of the viewport. I'm guessing this is because the autoscroll is relative to the parent of the draggable, which is going to be permanently to the left of my cursor if it's past the left edge of the viewport.

here's a video of this happening:

https://github.com/clauderic/dnd-kit/assets/7226642/d24d6c90-4d14-4938-85c7-33aa0d89b458

Here you can see that I am not able to autoscroll back to the "Open" column. because of DragOverlay I am also not able to manually scroll to get the leftmost column in view either.

Code here's the code of the `App.tsx` file for this. All the dnd-kit logic is in this file (though each column's `Droppable` is part of the `List` component): ```tsx import { Board, List, Card, Draggable, AppBar, NewListButton, NewListForm, } from "./components"; import { lists as initialLists } from "./data/lists"; import { getCardById, cards as initialCards } from "./data/cards"; import { useState } from "react"; import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, } from "@dnd-kit/core"; const App = () => { const [lists, setLists] = useState(initialLists); const [cards, setCards] = useState(initialCards); const [draggingId, setDraggingId] = useState(undefined); const [addingList, setAddingList] = useState(undefined); const handleDragStart = ({ active }: DragStartEvent) => { setDraggingId(active.id.toString()); }; const handleDragEnd = ({ over, active }: DragEndEvent) => { setDraggingId(undefined); if (over) { const activeCard = cards.find((c) => c.id.toString() === active.id); if (activeCard) { setCards((prevCards) => [ ...prevCards.filter((c) => c.id.toString() !== active.id), { ...activeCard, list: parseInt(over.id.toString(), 10) as number }, ]); } } }; const handleAddList = (index: number) => (name: string) => { setLists((prevLists) => [ ...prevLists.slice(0, index), { id: Date.now(), name }, ...prevLists.slice(index), ]); setAddingList(undefined); }; const handleDeleteList = (id: number) => () => { setLists((prevLists) => prevLists.filter((l) => l.id !== id)); setCards((prevCards) => prevCards.map((c) => { const newCard = c; if (c.list === id) { newCard.list = 0; } return newCard; }) ); }; const showNewListForm = (index: number) => () => { setAddingList(index); }; const cancelAddingList = () => { setAddingList(undefined); }; const isAddingList = addingList !== undefined; const isDragging = draggingId !== undefined; return (
{lists.map((list, index) => ( <> 0 ? handleDeleteList(list.id) : undefined} > {/** The children of `List` are wrapped in a Droppable in the List component */} {cards .filter((c) => c.list === list.id) .map(({ id, title }) => { return ( {title} ); })} {isAddingList ? (
) : ( )} {isAddingList && addingList === index && ( )} ))} {isDragging ? ( {getCardById(parseInt(draggingId))?.title} ) : null}
); }; export default App; ```