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

Child node morphs are not stable against DOM changes #31

Open
pvande opened this issue Jan 17, 2024 · 2 comments
Open

Child node morphs are not stable against DOM changes #31

pvande opened this issue Jan 17, 2024 · 2 comments

Comments

@pvande
Copy link

pvande commented Jan 17, 2024

I stumbled onto this while I was building an HTML change auditing interface.

If I have a node containing multiple children…

<div>
  Foo
  <span>Bar</span>
</div>

… and a target markup that outputs the same children, with different content…

<div>
  Boo
  </span>Bar</span>
</div>

… and a beforeNodeMorphed callback that changes changes the number of children…

Idiomorph.defaults.callbacks.beforeNodeMorphed = (oldNode, newNode) => {
  if (oldNode.nodeType !== Node.TEXT_NODE) return
  if (oldNode.nodeValue === newNode.nodeValue) return

  const fragment = document.createDocumentFragment()
  computeDiff(oldNode.nodeValue, newNode.nodeValue, {
    same(text) { fragment.appendChild(text) },
    insert(text) {
      const ins = document.createElement("ins")
      ins.innerText = text
      fragment.appendChild(ins)
    },
    delete(text) {
      const del = document.createElement("del")
      del.innerText = text
      fragment.appendChild(del)
    },
  })

  oldNode.replaceWith(fragment)
}

… then one or more of Idiomorph's internal iterators seem to get confused, leading to skipped or duplicated content.

@1cg
Copy link
Contributor

1cg commented Feb 4, 2024

yeah, mutating a node set in a callback is gonna give you a bad time. We could copy the nodes but it would be a big perf issue. Can you do your work before or after the morph?

@pvande
Copy link
Author

pvande commented Feb 5, 2024

I'm using the morph to compute a diff, so I can't make my changes beforehand, but I can queue them (either with an array outside the callback or with a setTimeout call). The ergonomics are worse, but it's functional.

At a glance, the behavior traces back to here, which seems to be the only place where the oldNode pointer is updated after the call to beforeNodeMorphed, which makes it seem as though this behavior could be allowed with minimal risk or effort.

(The next-best option for allowing mutations in that callback is probably using Array.from to create "snapshots" of each Node's childNodes and iterate through them by index, though that would require a more substantial refactor and add some processor and memory overhead.)

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

No branches or pull requests

2 participants