JS.hide in phx-remove is switching the element position

I am displaying a list of entities based on a Flop filter and I am trying to ease-out animate an element, if it is no longer included by the current filter, because for example, the status of the element changed. Now I am encountering the issue, that once the event is triggered that removes an element from the list, that element is placed below the next element, before fading out.

If the list looks like this:

[#1 Element]
[#2 Element]
[#3 Element]
[#4 Element]

And if the #2 element is updated and is no longer included in the filter, the list looks like this while the element is fading out:

[#1 Element]
[#3 Element]
[#2 Element] → fading out
[#4 Element]

I render the list like this in the parent view:

<.live_component
        :for={element<- @elements}
        module={FooWeb.FooLive.BarItem}
        id={"element-#{element.id}"}
        element={element}
      />

The live component:

@impl true
  def render(assigns) do
    ~H"""
    <article
      phx-remove={
        JS.hide(transition: {"ease-out duration-1000", "opacity-100", "opacity-0"}, time: 1000)
      }
    >
    BODY
    </article>
    """
  end

The elements list is assigned on each handle_params and on the handle_info call back that is recieving the updated element messages. Each function assignes a new list of elements. What can I do to prevent the reordering of the elements while the missing one is fading out?

Here is a guess:

Instead of using phx-remove, use a phx-click with the animation and a subsequent push-event(“delete”)

I suspect the event of DOM-removal and the animation cross each other, causing the removed div to take ‘unexpected’ space and the other divs are wrapped around it.

The re-ordering you see is actually the browser trying to place ‘the next item’ at the place of the ‘fading item’ and resolve the display overlap in a way you don’t like :wink:

I do wonder my theory after reading the docs:

:blocking - A boolean flag to block the UI during the transition. Defaults true

I think I figured it out. When I give the whole list of elements to the child component and :for loop it there, the issue does not occur. I changed the parent view like this:

<.live_component
        module={FooWeb.FooLive.BarItem}
        id={"element-list}"}
        elements={@elements}
      />

And the child:

@impl true
  def render(assigns) do
    ~H"""
    <div>
    <article
      :for={element <- @elements}
      id={"element-#{element.id}"}       <-- ID is important otherwise it is added below
      phx-remove={
        JS.hide(transition: {"ease-out duration-1000", "opacity-100", "opacity-0"}, time: 1000)
      }
    >
    BODY
    </article>
    </div>
    """
  end

Now the element fades out at the position where it is present in the list and no swapping is happening.