angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.23k stars 6.7k forks source link

CDKDragAndDrop: Collapse cdkDropList item contents on cdkDragStarted event #18179

Open nrslade opened 4 years ago

nrslade commented 4 years ago

(Marked as troubleshooting since I wasn't sure if there was a known solution for this case)

What are you trying to do?

Collapse/hide the contents of all items in a cdkDropList upon starting a drag event, leaving a condensed view of labels that is easier to sort.

What troubleshooting steps have you tried?

When starting the drag event, all items collapse except for the item currently being dragged, causing issues in the list placeholder when attempting to drop an item. Once the drop event is complete, the dropped item collapses and the list returns to normal.

Reproduction

StackBlitz: https://stackblitz.com/edit/components-issue-bt4kvt?file=app%2Fapp.component.ts

Steps to reproduce:

  1. Set up a cdkDropList with items that contain a title and collapsible content panel.
  2. Set the cdkDragStarted event for the list items to collapse all of the content panels.
  3. Make sure all items in the list are expanded.
  4. Begin dragging one of the items.

Environment

franciscojoseramosespinar commented 4 years ago

Im trying only collapse the item that has dragged and i have the same result, it only collapse the list after drop, not when drag started.

liesahead commented 3 years ago

Issue still persists in material v11.2.2. If someone is interested in a workaround:

Steps:

  1. On drag start set some classname on all list items which would hide content you don't need on drag through css. For example, we left only header visible, and set display: none for everything else. Something like that:
    public dragStart({ children }: HTMLOListElement): void {
    for (let i = 0; i < children.length; i++) {
        children[i].classList.add('cdk-drag-collapsed-item');
    }
    }
  2. Remove that classname with same approach on drop/release/drag end (whenever you want) to expand items back.

Vanilla JS classList used to avoid change-detection to run (which could cause performance drop for a few moments and also could require some additional workaround steps).

However, in some cases it still may look/feel buggy, so official fix would be really nice to have.

UPDATE: "I don’t think it’s beneficial to collapse groups automatically when the user starts dragging a row. That would interrupt the user’s action by moving everything around while he’s trying to interact with it, creating a moving target. To mitigate that problem somewhat, you could require that the user enter a reorder mode (similar to what you might be familiar with in iOS) in which groups are collapsed." (https://ux.stackexchange.com/a/82524)

liesahead commented 1 year ago

This issue is still important

liesahead commented 1 year ago

Up. Our current workaround:

To call expandDraggableListItems on cdkDragStart To call collapseDraggableListItems on cdkDragEnd

export abstract class DragDropUtils {
    private static _listDraggingClassName = 'pl-drop-list-dragging';

    // Workaround due to draggable content not collapsing on drag issue: https://github.com/angular/components/issues/18179
    public static expandDraggableListItems(dropList: HTMLElement): void {
        dropList.style.paddingBottom = '';

        dropList.classList.remove(DragDropUtils._listDraggingClassName);
    }

    // Workaround due to draggable content not collapsing on drag issue: https://github.com/angular/components/issues/18179
    public static collapseDraggableListItems(dropList: HTMLElement): void {
        const childrenArray = Array.from(dropList.children) as HTMLElement[];
        let initialHeightSum = 0;
        let updatedHeightSum = 0;

        for (const child of childrenArray) {
            initialHeightSum += child.offsetHeight;
        }

        dropList.classList.add(DragDropUtils._listDraggingClassName);

        for (const child of childrenArray) {
            updatedHeightSum += child.offsetHeight;
        }

        dropList.style.paddingBottom = `${initialHeightSum - updatedHeightSum}px`;
    }
}