Re: [w3c/webcomponents] Need "slotchange" event (#288)

As an end-user, having a `slotchange` event feels against the grain of MutationObserver movement because then why don't we just continue to use the deprecated DOM Mutation events the same way? Using MutationObserver would *follow new and recommended patterns* (not considering performance).

With the current `slotchange` event, I am running into the automatic requirement that I must now calculate myself which nodes were added or removed (which may be O(n)), but, otherwise, why on earth would I even care about the `slotchange` event? Why not use MutationObserver with an option to opt-in to distribution changes just like `childList`? Performance of an opt-in feature is O(1) when not opting-in (a conditional check).

@rniwa said,

> I think the use case matters here. It looks like the all use cases listed here are about updating the UI / rendering to accommodate changes in the distributed nodes, and not so much about updating DOM / API surface for component users. So it's actually desirable for those updates to not take place until the end of the current task.

I do that myself by deferring my render logic to an animation frame. But, not all users may be adept enough to do that, and those users may not necessarily care about animation anyways. In fact, those users probably will never touch MutationObserver.

@annevk said,

> If you do your own bookkeeping, you can replay. This is similar to attribute records without old values. But instead here we don't even provide a way to get to the old value (you have to store it yourself) and new value you have to get yourself too through a method.

What are the use cases where people want to listen to `slotchange` but *don't* want to see what the changes were in the distributed nodes? In my case (coincidentally?) I must detect which nodes were distributed or undistributed, otherwise `slotchange` isn't useful for me. For perspective, my lib is tracking the flat tree in order to render it to WebGL, so knowing which nodes are assigned/distributed is required.

@rniwa said,

> With slotchange, only thing we can observe is that the list of distributed nodes have changed. It doesn't tell us which nodes are added or removed into/from where.
> 
> Because of this, I'm opposed to using mutation observers unless the list of nodes that got inserted and removed are also provided.

Yes, please. I myself at least don't have any other reason to listen to slotchange other than to get the list of nodes that got inserted and removed. Is it coincidence that the first time I need to use `slotchange` I also need to calculate which nodes were added/removed?

> But @hayatoito and I both agree that doing so would be prohibitively expensive in both Blink and WebKit so I don't think this is an option.

If we use MutationObserver, with an opt-in option, how is that prohibitively more expensive than having to do it manually in JavaScript? It's not expensive at all if users don't opt-in similarly to childList, O(1).

@treshugart said,

> We haven't come up with a use case warranting knowing the exact changes yet for a slot change, but we also haven't really used the event in anger yet.

For my lib to be able to keep track of the final flat tree (for rendering in WebGL), this is needed. When nodes are assigned into slots, they have new parents with respect to the WebGL render tree (i.e. they are transformed relative to their slot parent). My lib keeps track of two things: when nodes are "possibly distributed" in which case they are not rendered relative to their original parents, and when those same nodes are "assigned", in which case they are rendered relative to their slot parents. So, when adding a shadow root to a host, the host's children automatically become "possibly distributed" and will not be rendered unless they happen to be distributed to a slot, and the rendering happens relative to that new location.

For more perspective, I believe that if a library like http://aframe.io (which renders Custom HTML Elements to WebGL via Three.js) wants to be compatible with ShadowDOM, then that library will also have to know how nodes are distributed (i.e. keep track of the flat tree), and the most obvious way to do that is keeping track of nodes that are assigned/unassigned to slots. Using MutationObserver for this would be much cleaner than making us do it in user-land.

> > Then, that could affect the distributed nodes of another slot element S2 to which S1 had been assigned. If there is such a slot S2, we'd check whether S2 is assigned to another slot for the same reason, and so on and so forth.
> 
> And all of them get an event, right? Thank you for mentioning that.

This is problematic for me. I would much rather have *only the final slot* fire the event, because that would guarantee that I have an easy way to know the final location where rendering of the distributed node will be relative to. Currently, if all slots fire the `slotchange` event, then hopefully those happen in order from most-shallow to deepest because then on the last `slotchange` I can record the final location of the distributed node. However, if the deepest slot is removed, the `slotchange` event will fire on that deepest slot, but not on any other slots. This means that if I want to record the next-less-deep slot I will need an array onto which I push slots (assuming they are pushed in the same order as the events from shallow to deep). Then, when a `slotchange` happens on any slot and the element is removed from that slot, I can simply `splice` my array from that slot index all the way to the end. Then, when I wish to know which slot an element is distributed to, I can simply look at the last item in the array (`slots[slots.length-1]`) to determine where in the flat tree I'll be rendering in WebGL.

On the other hand, if we had guarantee that distribution events fire only for the final positions of a distributed node, things would be much cleaner for my case (though I'm not considering other cases I may not have thought of). For example, if slot S1 is distributed to slot S2, which is distributed to S3, which is distributed to S4, then a node assigned to S1 would be finally distributed to S4. For my use-case, the ideal would be that a `slotchange` event fires on S4, and I keep track of the parent of S4 that way, for rendering purposes. If for some reason S3 is unassigned from S4, then two `slotchange` events would fire: one on S4 (and I can detect that the node was un-distributed) and one on S3 (where I detect the node is now distributed to).

If I was working with the A-Frame team, what I've just described is (I think) what I would need to implement to make the library ShadowDOM-compatible, which is somewhat complicated.

Also note, calling the distributed node an "assigned node" does not make sense because a node assigned to slot S1 is not assigned to S4 even if it is distributed to S4.

I think that besides `slot.assignedNode()` there should possibly be `slot.distributedNodes()` to find the final distribution. That would probably be more helpful and less confusing.

> ```
> insert
> 
>   The node is being inserted into a parent node that also has a shadow root
>     -> let slot be findSlot(node)
>     -> if slot is non-null:
>       -> run assignSlotables(slot) // this is naive, but good enough for spec
>       -> slotchange(slot)
> ```

Does that `slotchange(slot)` happen before the connected/disconnected events/callbacks of the node that is being inserted into the parent node? I think that since distribution is a less-likely event than connected/disconnected (i.e. all nodes have connected/disconnected events, but not all nodes are distributed), then it would be convenient for the slotchange event to fire first, which would make it easier to prevent some otherwise-normal connection/disconnection logic when the condition of distribution is detected, rather than canceling or reversing that logic after distribution is detected and it happens that both happened in the same tick synchronously.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/288#issuecomment-263005471

Received on Friday, 25 November 2016 18:01:44 UTC