[WICG/webcomponents] Options for controlling specifics of "slotchange" events? (#933)

By default a `<slot>` observes its children with just about the least complex options of a Mutation Observer. Essentially,
```javascript
new MutationObserver((mutationsList) => {
    for(const mutation of mutationsList) {
       mutation.target.dispatchEvent(new Event('slotchange'));
    }
}).observe(slotElement, { childList: true });
```
This means that a developer listening for the `slotchange` event can expect to get this event when direct child nodes are added or removed, but not when attributes on those nodes or text content of those nodes (when not elements) are changed. While this lower lever of complexity does well to ensure that a page full of `<slot>` elements is not ridiculously expensive to maintain, it does cause some surprise in developers leveraging this API for the first time (or for the 100th times as I find this really hard to actually keep in my brain). Once I am reminded of it, I often need to spin up my own Mutation Observer, anyways, which feels like it sort of defeats the perf benefit of keeping the `slotchange` trigger less complex in those cases. Here's an overly complex version of me doing just that: https://webcomponents.dev/edit/F4jBbQpeMSujg9FtRrtZ/src/index.ts

As this >100th time developer, and in the name of 1st time developers, I'd love to see a path towards being able to opt-in to more functionality here.

How could we supply our own options dict to that the above was more like the following?
```javascript
new MutationObserver((mutationsList) => {
    for(const mutation of mutationsList) {
       mutation.target.dispatchEvent(new Event('slotchange'));
    }
}).observe(slotElement, developerSuppliedOptions || { childList: true });
```
I'm not sure if this actually uses a Mutation Observer under the covers or not (though at one point, I thought heard that it did), but I'd like to start a conversation around the implication that it should (if it's not) and the possibility of being able to customize the options dict sent to `observer()` in the case that it was. Below I'll outline a straw person for some options to being able to do so.

I look forward to your patient support in this endeavor as I've followed the process of conversations like this in the past, but this is the first that I can remember attempting to start.

## All of my slots should act like this

In the case that a developer wanted all of their slots to act like this, it would see that adding this to the `attachShadow()` options dict would be a really useful idea.
```javascript
constructor() {
   super();
   this.attachShadow({ type: 'open', slotChangeOptions: { ... });
}
```
This would bind the same options dict to observers on _every_ slot in the newly created shadow tree, yay! 

With the recent inclusion of [Declarative Shadow DOM](https://web.dev/declarative-shadow-dom/), a tree global option like this implies that we might need to expand the attributes available on the `<template>` element in that context. We could use a single attribute with a custom parser (like we see in `srcset` et al), which would allow for a single attribute:
```html
<host-element>
  <template shadowroot="open" slotchangeoptions="...">
    <slot></slot>
  </template>
</host-element>
```
Or we could set more specific attributes:
```html
<host-element>
  <template shadowroot="open" slotchangesubtree slotchangechildlist etc.>
    <slot></slot>
  </template>
</host-element>
```
I this approach, `attributeFilter` accepts a non-boolean data type, so a custom parser may be required in either approach.

## Individual slots should opt-in

In many cases, a developer only wants to upgrade a single `<slot>` element to these capabilities, in keeping with the original intention of the specification, and in that case it might be better to submit the options dict directly on the `<slot>` element itself. This approach has the side benefit of allowing the same API no matter how your shadow tree is created, which likely adds implementor simplicity, but once again we have the option to apply the dict as a single attribute, which a custom parser:
```html
<template>
   <slot options="..."></slot>
</template>
```

Or as multiple attributes:

```html
<template>
   <slot subtree childlist etc.></slot>
</template>
```
Again, `attributeFilter` would likely need a custom parse in this context, but there is much more established art around collecting a list of strings than there is in collecting an object of booleans and arrays from an attribute.

## Alternatives?

I'm sure there are a number of other ways to approach this, and I'd be happy to append them right here as they come to mind. Finding a way to simplify developer interactions with slotted content, and possibly open a way towards further simplicity and flexibility in this are is quite exciting.

---

Thanks in advance for your thoughtful feedback on this idea. I look forward to where the conversation takes us!

-- 
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/933

Received on Friday, 25 June 2021 13:00:21 UTC