Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
chadlavi opened this issue Jun 4, 2024 · 0 comments

Comments

@chadlavi
Copy link

chadlavi commented Jun 4, 2024

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:

20240604-dndkit-autoscroll.mov

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):

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<string | undefined>(undefined);
  const [addingList, setAddingList] = useState<number | undefined>(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 (
    <div className="flex flex-col h-screen max-h-screen">
      <AppBar />
      <DndContext onDragEnd={handleDragEnd} onDragStart={handleDragStart}>
        <Board>
          {lists.map((list, index) => (
            <>
              <List
                key={list.id}
                id={list.id.toString()}
                title={list.name}
                onDelete={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 (
                      <Draggable key={id} id={id.toString()}>
                        <Card key={id} id={id.toString()} title={title}>
                          {title}
                        </Card>
                      </Draggable>
                    );
                  })}
              </List>
              {isAddingList ? (
                <div className="w-0 border border-transparent" />
              ) : (
                <NewListButton
                  last={index === lists.length - 1}
                  onClick={showNewListForm(index)}
                />
              )}
              {isAddingList && addingList === index && (
                <NewListForm
                  onCancel={cancelAddingList}
                  onSave={handleAddList(index + 1)}
                />
              )}
            </>
          ))}
        </Board>
        <DragOverlay className="cursor-grabbing" wrapperElement="ul">
          {isDragging ? (
            <Card
              key={draggingId}
              id={draggingId}
              title={getCardById(parseInt(draggingId))?.title}
            >
              {getCardById(parseInt(draggingId))?.title}
            </Card>
          ) : null}
        </DragOverlay>
      </DndContext>
    </div>
  );
};

export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant