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

best way to handle arbitrary grouping? #569

Open
aaron-barbieriaghib opened this issue May 6, 2024 · 13 comments
Open

best way to handle arbitrary grouping? #569

aaron-barbieriaghib opened this issue May 6, 2024 · 13 comments
Labels
question Further information is requested

Comments

@aaron-barbieriaghib
Copy link

I'm looking for a way to be able to drag an element into another one to create a group. Im not looking for infinite nesting, just a single layer. The main issue im running into is that when i drag an element out of a group and back into the base layer, i need to wrap that element in a dnd zone (so that i can drag other elements into it to create a new group). Conversely, when i drag a base level element into an existing group, i need to be careful to prune the empty dnd zone that i've "left behind" (this is much easier).

I'm struggling to come up with a way of representing the data such that it won't require conversion on drag, since all the zones have the same type. Would really love some help with this!

@isaacHagoel
Copy link
Owner

let me see if i understood:
you start off with a normal list (single level)
you want to drag an element into another so that it becomes a child ?
but this is only allowed to happen at the top level.
is that right?
if so, why do you need to dynamically add/remove zones?
maybe a simple drawing would help me understand it better

@aaron-barbieriaghib
Copy link
Author

For sure. Let's say we have three items
image

I want to be able to drag item 2 into item 1 (or any other item) to create a group.
image

and then drag item 2 back out to get back to the original state
image

Groups should be able to be arbitrarily large, but once an item is part of a group, that's as far as the nesting can go. It's very possible im overcomplicating this, but in order for each item to be able to "start" a group, doesn't it have to be it's own dnd zone? Happy to provide additional details if it would be useful!

@isaacHagoel
Copy link
Owner

that's helpful, is it possible to reorder the list by dragging items or only to group them while maintaining the original order?
if the answer is "possible" can you explain how that would work? as in when item 1 is above item 2 how do we know whether it "wants" to take its place or be grouped with it?

@isaacHagoel isaacHagoel added the question Further information is requested label May 6, 2024
@aaron-barbieriaghib
Copy link
Author

aaron-barbieriaghib commented May 6, 2024

Yes, items should still be sortable. This would work similar to how the crazy nesting example works from the docs (in this example, i can drag item 5 all the way to the base level, or i can drag it into the node 3 or node 4 group.

For my use case, if each item has it's own dnd zone, there's 3 situations that together create the behavior im looking for.

1. Sorting at the base level
Let's say we want to swap the order of item 2 and 3. This might be one way to do it. Item 2 gets dragged out of its dnd zone and gets placed after item 3. The empty dnd zone left behind is pruned (either on consider or finalize, not sure), and once item 2 is placed (on finalize), it's wrapped in a dnd zone.

Keep in mind that for each imagine, the entire thing is a dnd zone as well (in addition to each item's dnd zone).
image

2. Dragging an item into a group
If we want to drag item 2 into item 1 and create a group, it might look something like this. We drag item 2 into the dnd zone of item 1, which leaves item 2's zone as empty so we prune it.
image

3. Dragging an item out of a group
To remove an item from a group, we drag it out, and on finalize, wrap it in a dnd zone.
image

Of course, sorting items within a group is trivial and shouldn't require any data conversion.

Does this sound reasonable? If possible, I would love some help with the implementation. It's not totally clear to me how to deal with the pruning of empty dnd zones and the wrapping of items inside new dnd zones. If you can think of a simpler way of accomplishing this, i'd love to hear it!

@isaacHagoel
Copy link
Owner

I see, so the basic structure is:

  1. N dndzones with one item in each
  2. N+1 empty dndzones, one between each two items, one above the first item, one below the last item - these ones are in order to reorder items, they have low height and no border
    Correct?
    There is no need for the top level container to be a dndzone with this setup because you have the "in between" zones. In other words - no nesting.
    The in between zones will need to be re-created after each finalize to meet requirement 2 above.
    Did I miss anything?

@aaron-barbieriaghib
Copy link
Author

oh i hadn't even thought of that! As far as i can tell, that should handle everything as long as the "in between" zones are recomputed after every finalize (after re-ordering, dragging an item into a group, and out of a group).

Would you be able to create a bare minimum REPL for this? I think it might be a useful example to include in the docs.

picture to make sure we're on the same page:
image

@isaacHagoel
Copy link
Owner

isaacHagoel commented May 7, 2024 via email

@aaron-barbieriaghib
Copy link
Author

aaron-barbieriaghib commented May 11, 2024

I made a simple version of what you described, but it's quite jittery and difficult to use. I'd probably need to implement a rule that disallows dragging an item into the empty zone directly above or below it (to prevent useless layout shifts), but even then, im not sure it would ever be as smooth as a solution that makes use of an outter dnd zone. Any ideas?

<script>
  import { flip } from 'svelte/animate';
  import { dndzone, TRIGGERS } from 'svelte-dnd-action';
  const flipDurationMs = 300;

 let itemGroups = [
	  [],
    [{ id: '1', name: 'Item 1' }],
    [],
    [{ id: '2', name: 'Item 2' }],
    [],
    [{ id: '3', name: 'Item 3' }],
	  [],
  ];

  function handleDndConsider(e, index) {
	  itemGroups[index] = e.detail.items 
  }

  function handleDndFinalize(e, index) {
		if (e.detail.info.trigger === TRIGGERS.DROPPED_INTO_ZONE) {   
			// item has entered group
				itemGroups[index] = e.detail.items
				itemGroups = [[], ...itemGroups.filter((group) => group.length > 0).flatMap((e) => [e, []])];
		}
  }
</script>

<div style="display:flex; flex-direction: column; gap: 0px; width:150px">
{#each itemGroups as group, index (index)}
  <div 
		use:dndzone={{ items: group, flipDurationMs }} 
		on:consider={(e) => handleDndConsider(e, index)} 
		on:finalize={(e) => handleDndFinalize(e, index)}
		style="display:flex; flex-direction: column; gap: 5px;"
		class:multiple={group.length > 1} 
		class:empty={group.length === 0}
		>
    {#each group as item (item.id)}
      <div animate:flip="{{duration: flipDurationMs}}" class='item'>{item.name}</div>
    {/each}
  </div>
{/each}
</div>

<style>
	.item {
		background: pink;
		color: black;
		border-radius: 3px;
		padding:2px;
	}
	.multiple {
		margin-left: 10px;
	}
	.empty {
		min-height:15px
	}
</style>

@isaacHagoel
Copy link
Owner

Hi,
I played with it a little as well and made this
https://svelte.dev/repl/b950645b95b347dda9493278761bf284?version=4.2.16
there is still a bug in it that i didn't have time to fix (see TODO)
and overall I am not happy with it yet, sending it now even though it's incomplete in case it could still help somehow

@aaron-barbieriaghib
Copy link
Author

yeah this is already so much better than what i had! I'll take a look at the bug in the next few days and report back

@aaron-barbieriaghib
Copy link
Author

ok I looked into it, and the issue is that the readjust function assumes that whenever an item leaves behind an empty zone (because it's dragged somewhere else), it's sandwiched by 2 other empty zones – the one right before it and the one right after. This is correct, unless you drag the item into the empty zone right below it, in which case the i += 2 actually deletes the zone containing the newly dropped item.

here's a version of the repl that solves this issue, and also disables directly adjacent in-between zones if the group length is one.
https://svelte.dev/repl/88a4c553579847d29bdb3fd0e6ec00bc?version=4.2.16

this is definitely getting closer. Happy to help with other improvements if you're looking to include this example in the docs btw!

@isaacHagoel
Copy link
Owner

@quangthandev Cool. Thanks for this. What do you feel is still missing in order for this to "be there"?

@aaron-barbieriaghib
Copy link
Author

I'm honestly quite happy with this. The only minor thing I would mention are the layout shifts onfinalize because of the introduction/removal of in-between zones. In an ideal world, nothing in the layout would change between the moment before i release the mouse and right after. Unfortunately, this seems quite tricky to do. Running the readjustment function onconsider seems to break everything (probably due to some race condition where multiple zones' onconsider are triggered?), so im totally happy to leave as is.

thanks for all the help throughout this btw, super appreciate it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants