d3lm / ngx-drag-to-select

A lightweight, fast, configurable and reactive drag-to-select component for Angular 10 and beyond
https://www.npmjs.com/package/ngx-drag-to-select
MIT License
293 stars 65 forks source link

[Feature Request] Parent Children drag event interactions #74

Open zhaodong-wang opened 5 years ago

zhaodong-wang commented 5 years ago

This is a feature request (or if it is already achievable, please let me know). Might be related to #70.

Let me describe it briefly:

Here is the StackBlitz for the MWE: https://stackblitz.com/edit/angular-sbkxtf

So as you can see, we have a container and we have some children in it. The container is dts-select-container and the children are draggable="true".

When I start dragging the child:

  1. The select event is fired, so we will see a tiny selection box
  2. The dragstart of the child fired, so we can drag around the child
  3. When we release the mouse, the dragging for the child ended, but at the same time, the selection box is re-activated immediately.

My expected behavior is like Google Drive: video1

Where dragstart will be firstly fired in the container (so selection box shows up), when some items are selected, dragstart will be fired for those children and some animations shows up, after dragend fired, the selection box will NOT re-activately immediately unless the next dragstart fired. So either dragstart will be fired in the parent or child depends on whether there are selected things.

It is achievable with ngx-drag-to-select?

@d3lm

d3lm commented 5 years ago

So I quickly took a look at your StackBlitz and also how Google Drive does it. I think this is not really the responsibility of this library. As far as I can see, Google Drive only allows to drag an element if it is selected. So you might need to enable/disable the drag-and-drop as well as the drag-to-select. So you would disable drag and drop for all items unless they are selected. If you start dragging an element that is selected, it allows you to drag that element elsewhere. In that case the drag-to-select would need to be disabled, or at least the selected items need to be disabled. If you drag on an element that is not selected, then you would just disable dragging and the drag-to-select kicks in.

I am wondering if this would require something like beforeDrag on the select-container so you can disable it before the selection is started.

@timdeschryver Curious to hear your thoughts on this.

zhaodong-wang commented 5 years ago

@d3lm Thanks for your quick response! Now I can do something like this with the disableDrag input. But this is a bit hacky. The idea is:

  1. I have a service storing the selectedItems (a BehaviorObject).
  2. The select-container subscribes selectedItems: if selectedItems.length > 0 I set disableDrag = true, otherwise I set it to false.
  3. All the draggable items (and they are also dtsSelectItem) subscribes selectedItems as well (within a customized directive): if selectedItems.length > 0 I set attribute draggable = true, otherwise I set it to false. Also, if this item belongs to selectedItems, I add a selected-item css class to it, otherwise I will remove it.

So can somewhat reproduce the Google Drive behavior, but still there are some differences:

  1. In gdrive, if I have selected multiple items, then click any of them (actually it is clicking the the selected area) and then start dragging, I will be able to drag all of them. That being said, the selectedItems is NOT updated due to the first clicking (and thus the styles remain unchanged, just opacity being smaller). However, in dts-select-container, if I click an item, the selectedItem will be updated to this item, so we lost all the selectedItems prior to the clicking.
    I can solve it in a "hacky" way though: I use (selectionEnded)="updateSelectedNodes($event)", where updateSelectedNodes($event) = (items) => {selectedItems.next(items.slice())}. So the selectedItems won't be updated due to "clicking". This can also replicates another effect (which is good): if I selected multiple items, then click any of them but do not start dragging, this item will be selected and selectedItem should be updated.
    However, this brings another issue below.
  2. In gdrive, when dragging the selection box, selectedItems WILL be updated. But since I only used selectionEnded so in my solution selectedItems won't be updated.
    Also, in gdrive, if I selected multiple items, then click some unselected items, I can start dragging another selection box. But in my solution, since I set disableDrag = true if selectedItems.length > 0, I won't be able to start another selection box.

So I think the feature I would like to have in ngx-drag-to-select is: Is it possible to "only" disableDrag after I have clicked any "selected" items (and don't update selectedItems due to this click).

Sorry, the description might be a little complicated. Please let me know if there are any thing unclear.

Below is a small demo for the features: demo2

zhaodong-wang commented 5 years ago

@d3lm I think beforeDrag could potentially solve this. Is it possible to add this?

d3lm commented 5 years ago

Yep, I think we can add this output. Do you think that should be enough to make it work with the DnD library from the Angular CDK?

Curious to hear your thoughts. Maybe you have a solution in mind that is less or even not hacky at all.

zhaodong-wang commented 5 years ago

I am not sure if this would work with Angular CDK Drag and Drop. In the Stackblitz I wrote my own handler for drag, dragstart, dragend etc. I didn't use Angular CDK.

I think in order to replicate the GDrive effect, we probably need to provide the following API such that the entire event chain is like follows:

  1. mousedown
  2. Detect whether the current mouse position lies in the selected area or not. Say this value is a boolean: inSelectedArea, emit this value.
  3. There is callback function beforeDrag($event) consuming this emitted value. So we can determine the subsequent actions. For example:
    1. If inSelecteArea = true, disable drag and select. So this mousedown will not updated selectedItems and if the drag event starts, selection box will not be generated. Thus the previously selected items can be dragged to somewhere else.
      But, if no drag event after mousedown, e.g., the user click onto any of the selected item, and then release mouse (mouseup), then this is not a drag event, and in this case, we shouldn't disable drag or select and the event handling should be similar to inSelectedArea = false. This is the tricky part.
    2. If inSelecteArea = false, everything works like what we have now: a new selection box will be generated and the previously selectedItems will be updated.

I am not sure if this is easy to implement. I went through the source code and it seems that there are a lot of callbacks registered in the pipe after mousedown. So it might not be easy to ensure beforeDrag happens before any of the drag events, selection box generation, and selectedItems updates.

What do you think?

zhaodong-wang commented 5 years ago

@d3lm do you have any comments on this? Curious to hear your thoughts.

filipweilid commented 5 years ago

Did you ever figure out how to do this?

d3lm commented 5 years ago

Hey, I need to revisit this and think about it. Was working on a different feature (range selection, #89) first. I ll definitely come back to this. Any help is much appreciated, and if you have concrete ideas on how this could work then please go ahead and post it here. I'd also love to see someone working on a PR 🙌