@hello-pangea/dnd Documentation

repository·main·Indexed 26 days ago

https://github.com/hello-pangea/dnd

A high-level, accessible, and performant drag-and-drop API optimized for React list interactions. It supports vertical, horizontal, and nested lists, offering strong keyboard and screen reader support. The library is built around three core components: <DragDropContext />, <Droppable />, and <Draggable />, and emphasizes a 'physicality' design principle to simulate natural object movement.

Tokens
36K
Snippets
56
Records
178
Agent score
87%

What's inside @hello-pangea/dnd

  1. Overview of @hello-pangea/dnd

    main
    @hello-pangea/dnd is a library providing beautiful and accessible drag and drop for lists in React. It is designed for vertical and horizontal lists, movement between lists, and nested lists. It emphasizes natural movement, high performance, and strong accessibility (keyboard and screen reader support).
  2. Accessibility features in @hello-pangea/dnd

    main

    @hello-pangea/dnd provides comprehensive accessibility support to ensure drag and drop interactions are available to users who do not use a mouse or touch interface. Key features include:

    • Full keyboard support: Enables reordering, combining, and moving items between lists using only a keyboard.
    • Keyboard multi-drag support: Allows selecting and moving multiple items via keyboard.
    • Keyboard auto-scrolling: Enables scrolling while interacting via keyboard.
    • Screen reader support: Includes built-in English messaging for screen reader announcements out of the box.
    • Browser focus management: Smart handling of focus states during drag operations.
    • Lighthouse compliance: The library is tested against Google Lighthouse to maintain high accessibility scores.
  3. Understand screen reader support in @hello-pangea/dnd

    main

    @hello-pangea/dnd provides built-in screen reader support in English out of the box. The experience is primarily focused on keyboard interactions.

    Key accessibility implementation details:

    • It does not use the HTML5 drag and drop API.
    • It does not use the deprecated aria-grabbed or aria-dropeffect attributes.
    • It uses ARIA live regions to provide real-time state updates (drag lifecycle announcements) to screen reader users.
  4. Understand the physicality design principle in @hello-pangea/dnd

    main

    The core design philosophy of @hello-pangea/dnd is physicality. The library aims to simulate the movement of physical objects rather than digital UI elements. This manifests in several ways:

    • No instant movement (no snapping): Items are never instantly moved. Instead, items animate out of the way during a drag, and items animate into their new positions upon drop.
    • Movement-based positioning: Instead of using drop shadows, lines, or clones to indicate a drop location, the library uses the natural movement of items to communicate where a dragged item will land.
    • Center of gravity logic: Dragging impact is determined by the item's center of gravity rather than the initial grab point. A list item is considered 'dragged over' when the center position of the dragging item crosses a boundary, and resting items move out of the way once the dragging item's center passes their edge.
    • Natural cross-list movement: Keyboard movement between lists is driven by simulated inertia, gravity, and collisions rather than simple index-based logic.
  5. Explore community projects and examples

    main

    The following community projects are based on react-beautiful-dnd and may have compatibility issues with future versions of @hello-pangea/dnd. They serve as inspiration or reference for implementing drag-and-drop features:

  6. Understand how items move out of the way during dragging

    main

    To maximize performance, items that move out of the way of a dragging item use CSS transitions instead of physics, allowing the GPU to handle the movement.

    The animation curve is composed of three distinct phases:

    1. Warm up period: Mimics natural response time.
    2. Quick phase: Rapidly moves the item out of the way.
    3. Long tail: A slower final phase to ensure text remains readable while animating.
  7. Customize drag starting announcements

    main

    To customize the message read when a user lifts a <Draggable /> (e.g., via the spacebar), use the onDragStart responder in <DragDropContext />.

    Best Practices:

    • Use Position, not Index: Instead of saying "index 1", say "position 2". Use position = index + 1.
    • Include Context: Since the API doesn't automatically know the list size (especially in virtual lists), you should manually include the list length and list name in your custom message for a better experience.

    Default Message: "You have lifted an item in position `${startPosition}."

  8. Reparent a `<Draggable />` using the Cloning API

    main

    When a parent element has a CSS transform applied, the position: fixed used during dragging may result in incorrect positioning. To fix this, you can use the Cloning API to move the dragging item to a different DOM location (like document.body).

    When using the Cloning API, the original <Draggable /> is removed during the drag, and a clone is rendered into a container specified by getContainerForClone (defaults to document.body).

    Note: Using the Cloning API is required for compatibility with virtual lists. For best results, ensure the clone is the same size as the original item to prevent displacement issues.

    function List(props) {
      const items = props.items;
    
      return (
        <Droppable
          droppableId="droppable"
          renderClone={(provided, snapshot, rubric) => (
            <div
              {...provided.draggableProps}
              {...provided.dragHandleProps}
              ref={provided.innerRef}
            >
              Item id: {items[rubric.source.index].id}
            </div >
          )}
        >
          {(provided) => (
            <div ref={provided.innerRef} {...provided.droppableProps}>
              {items.map((item) => (
                <Draggable draggableId={item.id} index={item.index}>
                  {(provided, snapshot) => (
                    <div
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      ref={provided.innerRef}
                    >
                      Item id: {item.id}
                    </div >
                  )}
                </Draggable>
              ))}
            </div >
          )}
        </Droppable>
      );
    }
  9. Understand Auto-Scrolling constraints and behaviors

    main

    Container Definition

    A container is defined as either a <Droppable /> that is scrollable, a <Droppable /> that has a scroll parent, or the window itself.

    Scrolling Constraints

    • Large Draggables: If a <Draggable /> is larger than the container on the axis you are attempting to scroll, auto-scrolling on that specific axis will be disabled. For example, a <Draggable /> taller than the window height will prevent vertical auto-scrolling, but horizontal auto-scrolling will still function.
    • Input Types: Auto-scrolling works for mouse, touch, and keyboard inputs. For keyboard users, the library manages a combination of <Droppable /> scrolling, window scrolling, and manual movements to ensure items reach their intended positions.
    • iOS Limitation: On iOS (Webkit) browsers, users may experience a noticeable shaking effect during auto-scrolling due to a known Webkit bug.
  10. Implement fixed layout strategy for table reordering

    main

    The Fixed Layout strategy is faster and simpler but requires that column widths do not change based on content. You can achieve this using table-layout: fixed or by manually setting specific widths (e.g., 50%) on cells.

    Implementation Options:

    1. Dynamic Display: Set display: table on the <Draggable /> row specifically while it is being dragged.
    2. Permanent Widths (Recommended): To avoid styling issues during dragging, set a permanent width on each <td> using inline styles or CSS (e.g., width: 100px). This avoids the need for event responders.
  11. Use natural drag animations with natural-drag-animation-rbdnd

    main

    The natural-drag-animation-rbdnd addon adds natural dragging animations to your drag-and-drop implementation.

    ⚠️ Warning: This addon is based on react-beautiful-dnd and might not work with future versions of @hello-pangea/dnd.

    https://github.com/rokborf/natural-drag-animation-rbdnd
  12. Reorder table rows with reparenting (Portals/Cloning)

    main

    When using reparenting (such as ReactDOM.createPortal or cloning) with table row reordering, you must handle the fact that moving a <tr> involves unmounting the old row and mounting a new one in the portal. This unmounting causes the loss of cell dimension information.

    To preserve cell dimensions during reparenting:

    1. In the componentWillUnmount lifecycle of the <tr>, read the current widths of the cells from the DOM and store these values in a location accessible outside the component.
    2. In the componentDidMount lifecycle of the new mounting <tr>, check if DraggableStateSnapshot.isDragging is true.
    3. If a previously recorded width exists, apply those stored widths to the new cells via inline styles.