You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[bug] lit-html: Moving HTML comments creates issue with template system #2089
changed the title [-][bug] lit-html: HTML comments get changed and therefore reference points for some template systems[/-][+][bug] lit-html: HTML comments get changed[/+]on Oct 13, 2021
changed the title [-][bug] lit-html: HTML comments (reference points) get changed[/-][+][bug] lit-html: Moving HTML comments creates issue with template system[/+]on Oct 13, 2021
@JaySunSyn I've spent quite a lot of time trying to create a "proxy" of the onEnd, onMove-events that would revert the changes to the DOM when an element is dragged/dropped. I ended up here when I saw some nasty bugs in certain scenarios when lit "forgets" about a DOM-element and basically ends up with duplicates.
I've seen people in the Vue-community having similar issues: #546 (comment)
I just wanted to get back here and share what I’ve learned and the approach that I took to get something that really works.
First of all, the problem that you outline @JaySunSyn is a real issue, these comments store information that Lit uses to be smart about how to update the DOM during update and we need to make sure that they are not tampered with.
The only way that I've found to make this work is to rely on Lit to do the DOM-reordering when the "drag" ends. So basically we need to restore the DOM to it's initial state when the drag ends, update the underlying array and make sure that Lit triggers a DOM-update.
I've created two elements drag-container and drag-item that work together and are rendered something like this
Inside the drag-container element I setup the Sortable, either on the drag-container it self or on the first div inside. However, I use a "proxied" version of Sortable that will store the DOM-structure when a drag starts and then revert it onEnd or onClone:
importSortablefrom"sortablejs";/*Lit-wrapper for SortableJS to revert DOM-manipulation*/exportconstLitSortable={create : create}functioncreate(container : HTMLElement,options : Sortable.Options) : Sortable{letonStart=options.onStart;letonMove=options.onMove;letonEnd=options.onEnd;/** Stores the child-structure of a container element. * key = container element * value = .childNodes of that container when drag started */varmap=newMap<HTMLElement,ChildNode[]>();options.onStart=(e)=>{// Create a new map to make sure no old childNode-state is kept from previous drags.map=newMap<HTMLElement,ChildNode[]>();// Store the childNodes of the container were the drag started.map.set(e.from,[...Array.prototype.slice.call(e.from.childNodes)]);if(onStart)onStart(e);}options.onMove=(e,originalEvent)=>{// When entering this method the first, each container will have it's initial DOM-childNodes.// We only add each container to the map the first time we see it.if(!map.has(e.from))map.set(e.from,[...Array.prototype.slice.call(e.from.childNodes)]);if(!map.has(e.to))map.set(e.to,[...Array.prototype.slice.call(e.to.childNodes)]);if(onMove)onMove(e,originalEvent);}options.onClone=(e)=>{if(!map.has(e.from))map.set(e.from,[...Array.prototype.slice.call(e.from.childNodes)]);if(!map.has(e.to))map.set(e.to,[...Array.prototype.slice.call(e.to.childNodes)]);}options.onEnd=(e)=>{// Here we loop over the map and restore the DOM-structure to it's initial state.// Something other than SortableJS will have to trigger Lit to update and re-order the children.map.forEach((children,container)=>{container.replaceChildren();children.forEach((child)=>{container.appendChild(child);});});if(onEnd)onEnd(e);}returnSortable.create(container,options);}
I then use the LitSortable to create a new Sortable container:
@enkelmedia Thank you for this very helpful solution! On my end this solution works but I am seeing a pretty noticable flickering when dragging items around due to the DOM swapping that is taking place. Have you noticed this as well and know of any solution?
Great solution! I modified it to work with MultiDrag:
let presortPrevious: ChildNode[];
onStart: e=>{// used to put the items back in their pre-sort position onEndpresortPrevious=e.items.map(item=>item.previousSibling)}
onEnd: e=>{const{ items, newIndicies, oldIndicies }=e// RETURN ELEMENTS TO PREVIOUS POSITIONS, 'CANCELLING' THE SORTpresortPrevious.forEach((previous,i)=>previous.after(items[i]))// ALLOW LIT TO UPDATE THE DOM BY MOVING ITEMS IN ARRAY// IMPORTANT: NEED TO REMOVE ALL ITEMS FROM ARRAY AND ADD THEM BACK AS A 2nd STEPconstmovedArrayItems=oldIndicies.map(({ index })=>myArray[index])// 1. REMOVE MOVED ITEMSconstindiciesToRemove=oldIndicies.map(({ index })=>index)constmyFilteredArray=myArray.filter((item,i)=>!indiciesToRemove.includes(i))// 2. ADD THEM ALL BACK INnewIndicies.forEach(({ index },i)=>myFilteredArray.splice(index,0,movedArrayItems[i]))myArray=myFilteredArray// REQUEST LIT DOM UPDATEthis.requestUpdate();// UPDATE ARRAY IN DATABASE IF DESIRED}
@jdcoldsmith I've had some issues with flickering, but not so much that it's been a problem for me. I'm still using the same code as outlined above. In my case I'm dragging between different nested containers. I have not had the time to try out your example but I'll try to do that, thanks for sharing your solution!
Also thanks to @acotton-ipd for sharing your code as well!
I kind of "left" this issue when I got it to work decent but I'll revisit and try to see if your solution(s) work in my scenario with nested containers. Did any one of you try with nested containers or dragging between containers? (not just storing within one single container).
Add add a visual mask to avoid visual clipping during the update. That's a but ugly, but it work. Maybe I miss something in my process, maybe that's the best we can do here.
That would be great to have a revert method that we can call onEnd directly in SortableJS.
Activity
[-][bug] lit-html: HTML comments get changed and therefore reference points for some template systems[/-][+][bug] lit-html: HTML comments get changed[/+][-][bug] lit-html: HTML comments get changed[/-][+][bug] lit-html: HTML comments (reference points) get changed[/+][-][bug] lit-html: HTML comments (reference points) get changed[/-][+][bug] lit-html: Moving HTML comments creates issue with template system[/+]enkelmedia commentedon Jan 16, 2024
@JaySunSyn I've spent quite a lot of time trying to create a "proxy" of the
onEnd,onMove-events that would revert the changes to the DOM when an element is dragged/dropped. I ended up here when I saw some nasty bugs in certain scenarios when lit "forgets" about a DOM-element and basically ends up with duplicates.I've seen people in the Vue-community having similar issues: #546 (comment)
I guess I have two questions:
Did you get anywhere with your attempts?
Did you find another solution?
enkelmedia commentedon Jan 20, 2024
I just wanted to get back here and share what I’ve learned and the approach that I took to get something that really works.
First of all, the problem that you outline @JaySunSyn is a real issue, these comments store information that Lit uses to be smart about how to update the DOM during update and we need to make sure that they are not tampered with.
The only way that I've found to make this work is to rely on Lit to do the DOM-reordering when the "drag" ends. So basically we need to restore the DOM to it's initial state when the drag ends, update the underlying array and make sure that Lit triggers a DOM-update.
I've created two elements
drag-containeranddrag-itemthat work together and are rendered something like thisInside the
drag-containerelement I setup the Sortable, either on thedrag-containerit self or on the firstdivinside. However, I use a "proxied" version of Sortable that will store the DOM-structure when a drag starts and then revert itonEndoronClone:I then use the
LitSortableto create a new Sortable container:I really hope that this might help others that is looking to implement SortableJS with Lit.
jdcoldsmith commentedon Jul 19, 2024
@enkelmedia Thank you for this very helpful solution! On my end this solution works but I am seeing a pretty noticable flickering when dragging items around due to the DOM swapping that is taking place. Have you noticed this as well and know of any solution?
jdcoldsmith commentedon Jul 19, 2024
Actually after doing some more searching I found that this workaround actually does the trick with much less code and no screen flickering! https://lit.dev/playground/#gist=242f45fd2dbe21ecb6902f144686aae8
eviltik commentedon Sep 2, 2024
@jdcoldsmith nice shot !! Working greats here
acotton-ipd commentedon Dec 5, 2024
Great solution! I modified it to work with MultiDrag:
enkelmedia commentedon Dec 13, 2024
@jdcoldsmith I've had some issues with flickering, but not so much that it's been a problem for me. I'm still using the same code as outlined above. In my case I'm dragging between different nested containers. I have not had the time to try out your example but I'll try to do that, thanks for sharing your solution!
Also thanks to @acotton-ipd for sharing your code as well!
I kind of "left" this issue when I got it to work decent but I'll revisit and try to see if your solution(s) work in my scenario with nested containers. Did any one of you try with nested containers or dragging between containers? (not just storing within one single container).
Cheers!
acotton-ipd commentedon Dec 13, 2024
@enkelmedia no I am always working with a single container.
jdcoldsmith commentedon Dec 16, 2024
@enkelmedia Same as @acotton-ipd, I only have used this in the context of a single container.
ger-benjamin commentedon May 16, 2025
Thanks @jdcoldsmith and all of you. The solution from @jdcoldsmith is here (just to have it in the ticket directly):
The
repeatlit directive is also a part of the solution (instead ofmap).Also, I don't know why, but in my case (with complex child elements), I had to make a first update with an empty array to make everything working.
Add add a visual mask to avoid visual clipping during the update. That's a but ugly, but it work. Maybe I miss something in my process, maybe that's the best we can do here.
That would be great to have a
revertmethod that we can callonEnddirectly in SortableJS.people-sea commentedon Feb 2, 2026
I made a plugin for this: sortablejs-pinned
It's a post-hoc fix — restores comment nodes after SortableJS moves them. Not ideal, but works until SortableJS handles this natively.