- From: Joe Pea <notifications@github.com>
- Date: Mon, 06 Sep 2021 21:43:25 -0700
- To: WICG/webcomponents <webcomponents@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <WICG/webcomponents/issues/940@github.com>
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: ```js // 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: ```js // 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. With slotchange/assigned* it is easy to get fidning 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. -- You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/WICG/webcomponents/issues/940
Received on Tuesday, 7 September 2021 04:43:38 UTC