illright / attractions

A pretty cool UI kit for Svelte
https://illright.github.io/attractions
MIT License
1.03k stars 37 forks source link

Input value disappears on drag&drop #364

Closed Dachaz closed 1 year ago

Dachaz commented 1 year ago

Hi there,

I've ran into an issue when trying to use any Svelte drag&drop library and Attractions TextField.

The use-case is reordering a list of items that each contain inputs (and other elements) (REPL). When using regular HTML inputs, the drag&drop finishes as expected, and the input value is preserved. With Attractions TextField, the TextField values get blanked out (both on the item being dragged, and items that are dragged over), but the underlying object still holds the value (so, it's only a rendering issue). I've tried several different drag&drop libraries yesterday, and the behaviour was the same with all of them. Tested in Safari, Chrome and Firefox — all exhibit the same behaviour.

Do you have any ideas of why this is happening, and how it could be worked around (without having to clone Attractions' styling on regular input elements)?

Thanks!

attractions

aabounegm commented 1 year ago

Hi @Dachaz, thanks for sharing a minimal REPL reproducing the bug. After inspecting it and playing around with it for a bit, I think I understand why the bug is occurring. The svelte-dnd-action library overrides the id of the object you're dragging to one that didn't exist before as in this screenshot: image I think this might be messing with Svelte's keyed-{#each}. I can't say I'm familiar with internals of how it works, but I think that it destroys the old component (since the key is id, and the given id no longer exists in the list), and then creates a new one when the DND library restores the id again. Also, this comment feels relevant to me: https://github.com/isaacHagoel/svelte-dnd-action/issues/247#issuecomment-775347006 Unfortunately, I was not able to find a workaround, and I'm not sure if the bug is because of Attractions, the DND library, or even Svelte itself

aabounegm commented 1 year ago

Ok, after some more tinkering, I seem to have found the root cause, but I still don't completely understand why it is that way.

For some reason, I cannot log in to the Svelte REPL, so here is the code **`App.svelte`**: ```svelte
{#each items as item (item.id)}
{item.name}
{/each}
``` **`MyInput.svelte`**: ```svelte ```

Shortly, it seems that the issue is with using a component since using an identical native HTML element works, which makes me think that it is an issue with how Svelte works. However, I also noticed that the component works if I use bind:value inside it as well, which we cannot do inside our TextField component if we want to preserve the number conversion behavior.

Dachaz commented 1 year ago

Hey, @aabounegm — thanks a lot for doing a deep-dive on this!

The comment you linked to, in turn, led me to SortableJS which, while not as elegant as svelte-dnd-action, doesn't seem to suffer from this issue: REPL

For the PoC I'm building, that's good enough, so, at least from my end — the issue can be closed :)

Dachaz commented 1 year ago

I spoke too soon. When I started modifying the underlying data model, ran into the same issue. So I'll experiment following your second advice.

aabounegm commented 1 year ago

I'm glad I could help, and sorry you faced other issues, but since it's not really a problem that can be fixed from within Attractions, I'm afraid there is not much more that we can do. Though I'd be happy to try and give more suggestions if you share the updated REPL.

Dachaz commented 1 year ago

I ended up making a SimpleTextField component that's used only in the drag&drop section. (REPL) It's not ideal, but Does the Job™ for this use-case. Would appreciate your input if there's a better way to borrow styling than what I've done here:

<script>
  /**
   * Attractions-styled input without all the extra functionality
   * See why here: https://github.com/illright/attractions/issues/364
   */
  import { classes } from "attractions/utils";

  let _class = null;
  export { _class as class };
  export let value = null;
</script>

<div class={classes("text-field", _class)}>
  <input bind:value {...$$restProps} />
</div>

<!-- svelte-ignore css-unused-selector -->
<style lang="scss">
  @use "~attractions/_variables";
  @use "~attractions/text-field/text-field";
</style>
aabounegm commented 1 year ago

I honestly can't think of a better way, but I still think there is some underlying problem either with how bind:value works or with how these libraries work. Sorry you had to go through the troubles of reimplementing the component 😅

Dachaz commented 1 year ago

No worries! You've been beyond helpful, and I really appreciate that.