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

distributionchange events, and distributedNodes() / distributedElements() APIs? #940

Open
trusktr opened this issue Sep 7, 2021 · 1 comment

Comments

@trusktr
Copy link

trusktr commented Sep 7, 2021

Even after the spec changes in #571, it's still not exactly obvious how to detect final distribution and ignore intermediary slotting (an element being slotted to a slot does not mean that's where the element is finally distributed to).

I asked and answered my own question on how to achieve the desired effect using slotchange and assignedNodes()/assignedElements() here:

https://stackoverflow.com/questions/69078150

A possible idea is that a distributionchange event (or distribution event, name bike sheddable)

  • would not bubble
  • would fire on any slots in a chain of slots (slot assigned to a slot assigned to a slot, where an element might tunnel these slots to its final distributed destination) only if final distribution to those slots changed.
    • event.target is always the slot that the event fires on, and event.target is always === event.currentTarget because there is no bubbling.

With that in place, people could call slot.distributedNodes() or distributedElements() in the event handler and

  • if the slot is assigned to another slot (assignedSlot is not null), then it must have no distributed nodes, so the return value is an empty [].
  • if the slot is assigned to another slot, it means that this slot was just slotted to a further slot, hence the result of distributed* API calls was previously not empty, but is now empty.
  • if the slot is not assigned to another slot, it must be the final distribution location of nodes or elements, so calling distributed* APIs on this slot will return a non-empty array (unless node or elements higher up were removed or slot atributes changed).

If there is a chain of 2 slots (across two shadow trees), and then the deeper 2nd slot is slotted to an even deeper 3rd slot in a 3rd deeper shadow tree, then distributionchange would fire only on the 2nd and 3rd slots, because no distribution changed for the 1st slot in the ancestor-most shadow tree. The only distribution change that happened is that elements went from being distributed to the 2nd slot location to now being distributed to the deeper 3rd slot location, so the event would fire for those two slots only. If more shadow roots are added between slots 2 and 3 and connect the slot distribution chain, no distributionchange events would fire because the final deepest slot is still the final place where nodes or elements are still distributed to.

It's a bit difficult to describe all this with words. so here is an example of code using slotchange and assignedNodes, and how the equivalent would be using distributionchange and distributedNode:

Current approach with slotchange and assigned* APIs:

// Assume this slotchange handler will be added to all slots in a hierarchy of shadow trees.
const slotchangeEventHandler = event => {
  // Always use `event.currentTarget` because that points to the slot
  // that this event handler is on, whereas `event.target` will
  // always point to the slot from the ancestor-most shadow tree.
  // That ancestor-most tree's slot is slotted to this slot (currentTarget)
  // or to some other slot that is ultimately distributed to this slot.
  const slot = event.currentTarget
  
  // Note, by using `currentTarget`, we effectively don't care about bubbling.
  // Also note that event delegation would be useless here, because `currentTarget` would be
  // the delegating element, and `target` would be the ancestor-most slot and never the
  // distribution slot, therefore neither is useful for the case of detecting final distribution.
  // Hence my suggestion of `distributionchange` not bubbling.

  // If the slot has an `assignedSlot`, then it can not possibly be the slot
  // where nodes or elements are finally distributed to, so return an empty
  // array in that case. Otherwise, the slot must be the final distribution
  // slot, in which case calling `assignedNodes` with `flatten:true` will give
  // the desired result (the finally-distributed nodes).
  const distributedNodes = slot.assignedSlot ? [] : slot.assignedNodes({flatten: true})

  // Similar if you just want elements (f.e. not text nodes):
  const distributedElements = slot.assignedSlot ? [] : slot.assignedElements({flatten: true})
})

Here is the same result using distributionchange and the distributed* APIs:

// Assume this distributionchange handler will be added to all slots in a hierarchy of shadow trees.
const distributionchangeEventHandler = event => {
  // Either one works, target and currentTarget are the same: the slot on which distribution changed:
  // People can't get this wrong, whereas with slotchange they which can lead to confusion and broken expectations.
  const slot = event.target

  // No need to think here, no need to add conditional logic, no need to set flatten to true, whereas
  // with slotchange it is easy to get this wrong:
  const distributedNodes = slot.distributedNodes()

  // Similar if you just want elements (f.e. not text nodes):
  const distributedElements = slot.distributedElements()
})

As you can see, the distributionchange + distributed* API approach is simpler, and is easy to get right. With slotchange and assigned* APIs, it's harder to get right; easy to get detection of final distribution wrong.

In a real-world project I'm working on, I only care about final distribution of nodes and elements, in order to render based on the composed tree using WebGL. In particular, I need to know when final distribution changes so that I can re-parent the objects that represent what to draw in WebGL, and the tree structure of WebGL objects therefore matches that of the DOM composed tree.

@trusktr
Copy link
Author

trusktr commented Jul 29, 2022

Do you see how much simpler this distribution API idea is for the end author?

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

1 participant