svelte-dnd-action
svelte-dnd-action copied to clipboard
How to implement a target zone with static contents?
I'm trying to implement a basic files-and-folders scenario, with customizable folder "icons", something like this:

The folders are supposed to be moveable and draggable like files, and to accept drops. I don't want to show any dynamic items inside their HTML elements (much less have users drag them). The trouble is, I do need them to have some extra HTML children (like the "A" in example pic above) for decoration that can't be done with CSS tricks (unlike the simple A example).
Using dragDisabled: true option on folders almost does it (you can drag them around and drop other items in them), but the trouble is that their decorations get mangled. As README puts it:
The dndzone should not have children that don't originate in items
What this means is, the following example gets "decoration stuff" elements hidden (style="visibility: hidden"), among other things, as user hovers stuff above it, due to how shadow elements work:
<script>
...
let dummy_dnd_items = [];
</script>
<div class="myFolder" use:dndzone="{{items: dummy_dnd_items, dragDisabled: true}}">
<div>
...lots of decoration stuff here
<span>Folder label</span>
</div>
<div> ...more decoration etc..</div>
...
First I tried making an empty handleConsider() that doesn't apply any changes that the library suggests, but that results in the dreaded getBoundingClientRect problems in handleFinalize(). Apparently the library expects shadow elements to be there.
I've tried to work around it with a monstrosity like this...
<div class="myFolder" use:dndzone="{{items: dummy_dnd_items, dragDisabled: true}}">
{#each dummy_dnd_items as item, i}
{#if i == 0}
{#each [1,2] as _x}
{#if x == 0 || item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
<div>
...lots of decoration stuff here
...
...
...but that lead to chaos with element size calculations in browser, and is pretty much unreadable anyway.
Is there some (clean) way to achieve this?
If not, maybe one could mark static target children with some class like dnd-dont-touch-this-its-not-a-zone-item so you could actually have children that don't come from items?
~~It sounds like PR #335 would allow this as well, you just wouldn't render the dummy_dnd_items for the folder div.~~
(Looks like it had issues of its own.)
Ok, I finally figured out a method that works:
<script>
let dummy_dnd_items: any = [{'id': 'dummy'}];
function handleConsider(e: CustomEvent<DndEvent>) {
// Move shadow item to the end of the list
// so first item is never hidden
dummy_dnd_items = e.detail.items
.filter(it => !it[SHADOW_ITEM_MARKER_PROPERTY_NAME])
.concat(e.detail.items.filter(it => it[SHADOW_ITEM_MARKER_PROPERTY_NAME]));
}
function handleFinalize(e: CustomEvent<DndEvent>) {
// <Handle drop logic here>, then
// clear the dummy item list again
dummy_dnd_items = [{'id': 'dummy'}];
}
</script>
<div use:dndzone="{{items: dummy_dnd_items, morphDisabled: true, dragDisabled: true}}">
{#each dummy_dnd_items as item, i}
{#if i == 0}
<div>
... static decoration stuff ...
</div>
{:else}<span/>{/if} <!-- without these invisible items, getBoundingClientRect error at finalize -->
{/each}
</div>
cool workaround but why not place that icon under the same parent as the dndzone and position:absolute it to your liking rather than making it a child of the dndzone?
I want the folders to change style (zoom and brighten) on dnd actions, which the drop zone action supports nicely, and have the decorations be included.
Bug with the workaround: works ok for mouse, but for keyboard navigation, the static decorations divs apparently get automatically assigned tabindexes from the library (even though dragDisabled: true) => bogus tab focus targets inside the folder element, and weird behavior when reordering by keyboard.
Maybe if the tabindex wasn't overridden if it's already set to -1..?
A way to define static items that the library ignores would be cleaner though. Or perhaps - for this use case - just a way to declare a dropzone targetOnly (=zone that can finalize() drops but doesn't have any managed items in it).
Feel free to make a repl and i will check if the tab index behaviour is a bug or better - help you get the absolute positioning solution working including the styling that you need. Ignoring some of the items was considered in the past but it adds a lot of complxity and edge cases and I can remember a case in which the desired behaviour couldn't have been achieved without it.
On Sun, Mar 5, 2023, 05:12 elonen @.***> wrote:
Bug with the workaround: works ok for mouse, but for keyboard navigation, the static decorations divs apparently get automatically assigned tabindexes from the library (even though dragDisabled: true) => bogus tab focus targets inside the folder element, and weird behavior when reordering by keyboard.
Maybe if the tabindex wasn't overridden if it's already set to -1..? A way to define static items that the library ignores would be cleaner though.
— Reply to this email directly, view it on GitHub https://github.com/isaacHagoel/svelte-dnd-action/issues/435#issuecomment-1454827774, or unsubscribe https://github.com/notifications/unsubscribe-auth/AE4OZC4SU7A3DACDUVYJWGDW2OA2JANCNFSM6AAAAAAVOW52DA . You are receiving this because you commented.Message ID: @.***>
Thank you. Here's the repl: https://svelte.dev/repl/82a7534845b44337b9cdaffdea034b6e?version=3.55.1
Thanks, just before I spend time on this, have you tried the zoneTabIndex option?
On Sun, Mar 5, 2023 at 9:05 PM elonen @.***> wrote:
Thank you. Here's the repl: https://svelte.dev/repl/82a7534845b44337b9cdaffdea034b6e?version=3.55.1
— Reply to this email directly, view it on GitHub https://github.com/isaacHagoel/svelte-dnd-action/issues/435#issuecomment-1455044516, or unsubscribe https://github.com/notifications/unsubscribe-auth/AE4OZC4UOPLW5F3TY45WJT3W2RQMZANCNFSM6AAAAAAVOW52DA . You are receiving this because you commented.Message ID: @.***>
Great suggestion, thanks! It's almost there now.
I managed to get the decorations out of Folder items with some CSS trickery. Combining that with zoneTabIndex: -1 and a finalize() variation that completely clears items after the drop very nearly does it.
Unfortunately, keyboard dropping now throws an exception. It feels like a dangling reference inside the library. Here's a new REPL with instructions on how to reproduce it: https://svelte.dev/repl/0efc86c2581340b0ac5b448b0fc407b9?version=3.55.1
To avoid this undefined.id exception, it probably should exit the keyboard drag state if dragged element is destroyed by a some finalize() (or even consider()).
Success! Version 3 seems to work perfectly with both mouse and keyboard: https://svelte.dev/repl/0c13450eff4347318dfc684342f86b6e?version=3.55.1
The code is surprisingly terse, but I had to use info.trigger and info.source in a rather groovy way to clear the item list at an appropriate state depending on the used input device:
function onSink(e) {
console.log("Sunk #" + e.detail.items[0].id + " into #" + id)
items = [];
}
function consider(e) {
if (e.detail.info.trigger == TRIGGERS.DRAG_STOPPED && e.detail.info.source == SOURCES.KEYBOARD) {
// On keyboard drag, DRAG_STOPPED on consider() is the _real_ "finalize" state
onSink(e);
} else {
items = e.detail.items;
}
}
function finalize(e) {
if (e.detail.info.source == SOURCES.KEYBOARD) {
// On keyboard, dragged item can be taken back out by another (shift-)tab key hit,
// so we have to keep in `items` for now:
items = e.detail.items;
} else {
// On pointer, finalize() is actually final. Sink the item.
onSink(e);
}
}
The exception mentioned in previous comment might be worth fixing anyway, though.
I'm fine with the solution for now, so if/when you're done looking into this @isaacHagoel, the issue can be closed. Thank you so much for the help!