Open Westbrook opened 12 months ago
I've also seen in a similar way the need for collections of DOM elements to be built based on their shared DOM scope (main complexity beyond
el.querySelectorAll(...)
being the elements living in different DOM trees, but the same DOM scope), which if it were a shared issue could likely benefit from a shared protocol, as well.
I'd be interested in any particular examples of this you can share. I'm struggling to convince myself that this is actually a generic problem; indeed, I'm not sure this problem is actually relevant to the slottables proposal. In the context proposal, the protocol needs to determine what element is providing the scope, and there are (unresolved) nuanced aspects such as establishing the scope after the request has been made and dynamically changing which element is providing the scope. But to me these issues feel specific to the context protocol, and details of the 'scope' don't seem to be important to the slottables. My interpretation of the slottables proposal is that the 'scope' is inherently just the parent component, so no scope resolution actually takes place.
Events versus DOM walking/well-known method names is definitely a question I'm interested in, even though I'm unsure it's relevant outside of contexts. The benefit of context requests being events is that it lets a consumer communicate with a provider without knowledge of the provider and without the responsibility of determining who the provider is. Using a bubbling event to traverse ancestors, including the ability for an ancestor to stop the bubbling at at a certain point, as a means of implementing context is a really clever idea, and I don't think I would have come up with it myself. I think the reason I wouldn't have come up with it, though, is because I'm unsure it's semantically correct. An event in the DOM is supposed to notify anyone who may be listening of an occurrence or change in state, but this isn't really what context requests are. Furthermore, I don't think an emitter should care or even have knowledge of whether anyone is listening, but context requests specifically expect a response. So overall I think I'm torn on the issue.
This is a simplified draft of these three use cases that I think it might be possible to support with a shared approach to communication across a DOM tree, but it's still quite theoretical, so thanks for your participation in the discussion! As I gather information here, I'm very much trying not to prescribe a solution, which is why I went with "DOM Scope Request Resolution" so that the conversation could flush out the best solution (or solutions) for the use cases available.
<slot>
or one things asks for many values.Relationships across the DOM tree are the important factor in each that leads me to position the question of there being a shared overarching protocol that could support clarifying them all. In each, the difference from "historic" element development, where the element is a mini-application, is the idea that there can be an external owner of a state-like entity. An external owner should not be considered "required", so a holistic protocol should clarify how the individual requester could "self-resolve" in all of the cases.
Number three might be flexible to broader use cases, but I specifically find the need for it in establishing list content across complex DOM trees. In a way, it's a process by which the client can create flattened trees. For example, a <select>
element resolves its relationship with <option>
elements and <optgroup>
elements by direct parent-child DOM structuring.
<select>
<option>0</option>
<option>1</option>
<!-- ... -->
<option>n</option>
</select>
<!-- OR -->
<select>
<optgroup label="Group 0">
<option>0</option>
<option>1</option>
<!-- ... -->
<option>n</option>
</optgroup>
<!-- ... -->
<optgroup label="Group N">
<option>0</option>
<option>1</option>
<!-- ... -->
<option>n</option>
</optgroup>
</select>
Requiring this relationship means that one could leverage el.children
, a single MutationObserver
, or similar to handle this use case within a custom element:
<x-select>
<x-option>0</x-option>
<x-option>1</x-option>
<!-- ... -->
<x-option>n</x-option>
</x-select>
Ignore for a moment the reality that <selectlist>
is coming, and it has slightly more relaxed relationships here, but what if the actual DOM in that comes together looks more like:
<composed-select>
#shadow-root
| <x-select>
| #shadow-root
| | <x-option>Default Option 0</x-option>
| | <x-option>Default Option 1</x-option>
| | <!-- ... -->
| | <x-option>Default Option N</x-option>
| | <slot></slot>
| <x-option>Applied Option 0</x-option>
| <x-option>Applied Option 1</x-option>
| <slot name="root"></slot>
| <!-- ... -->
| <x-option>Applied Option N</x-option>
| <x-optgroup>
| #shadow-root
| | <x-option>Default Option 0</x-option>
| | <x-option>Default Option 1</x-option>
| | <!-- ... -->
| | <x-option>Default Option N</x-option>
| | <slot></slot>
| <x-option>Applied Option 0</x-option>
| <x-option>Applied Option 1</x-option>
| <!-- ... -->
| <x-option>Applied Option N</x-option>
| <slot name="group"></slot>
| </x-optgroup>
| </x-select>
<x-option slot="root">Applied Root Option</x-option>
<x-option slot="group">Applied Group Option</x-option>
</composed-select>
Here, the collection that is being created is a coalescence of DOM elements from four different DOM trees. All of the options that should be collected share one or more ancestor elements any of which could be the scope at which the request was resolved.
Starting from the same, the options in the above example all have cost, at some point of scale, that cost will be such that you won't want to pay it unless the options are interactive (e.g. the <x-select>
is open
). This is where a slottable request would relate to this approach in that was <x-select>
to make that request when it is opening, then it would need to be resolved across three (or more) DOM trees to completely fulfill the expected interface.
- Slottable Request: a single descendant requests to have one or more ancestors resolve to address one or more DOM elements to a prescribed
<slot>
or one things asks for many values.
I don't think this is a correct interpretation of the proposal. The proposed slottable-request
event does not bubble, so there is no 'scope' in the way that you describe. I think the idea is just that the user who instantiated the component knows that it will dispatch these events and will listen for them, but this doesn't involve a relationship across the DOM tree; slottable requests are not actually made to an ancestor. Also, if an arbitrary ancestor was aware of and could respond to slottable requests, that would theoretically be a violation of encapsulation.
I agree that the select/option use case, or more generally the 'input group' use case, is interesting, but I've seen this problem before in React, and contexts is how people solve it; here's one source I could easily find.
You theorize that contexts are a specialization of the idea of a shared DOM scope, but part of me feels like contexts may actually be the generalization of the idea.
What's great about a well-written spec is that it is flexible beyond its original intentions. Much like the <slot>
spec likely didn't take into account stacked slots (e.g. <slot name="custom"><slot></slot></slot>
), the slottable request protocol doesn't have to explicitly outline the relationship I have for it to make it possible. Here's an example, if you want to follow along: https://studio.webcomponents.dev/edit/1g4cC4DRhZyQhetNqKBf/stories/index.stories.js?p=stories
However, that protocol is still very much in the ideation phase, and the discussion herein specifically calls out that it, the Context Protocol, and others may be better served by a more generic protocol. They may even be better served by entirely different mechanisms for achieving their stated goals, whether in terms of performance, ergonomics, or general understandability. In that way, I'm trying not to constrain what could be possible here by what may or may not be possible there.
If the request for items in a list can be resolved by a single context at the list host, this may not be a genuinely new use case. I'll spend some time with your link and see how it resonates. Immediately, the context owner and the collection owner seem to be the same (at least as leveraged in the article you shared), which is not true in my example. I may be misinterpreting, as I'm not much of a React developer, so I'll get back to you on that later.
In my example, it's important to understand, and maybe it's easier to do so in the live example rather than the example rendered DOM, that the owners of the individual items are different but the individual items themselves coalesce into a single collection. We're specifically seeing the benefit of this in organism-level components that exemplify a shared user experience but surface specifics of the consuming application. With this capability, the design system can say you need the "List " and "Group " items, but the application can say you need the "Composed " items and the state can say you need the "External " items.
What's great about a well-written spec is that it is flexible beyond its original intentions. Much like the
<slot>
spec likely didn't take into account stacked slots (e.g.<slot name="custom"><slot></slot></slot>
),
I disagree with this example. I don't think the 'stacked slots' behavior you show is especially interesting or surprising; I think it's the normal behavior of slots working exactly as intended (not to say you shouldn't have written about it; it's cool).
the slottable request protocol doesn't have to explicitly outline the relationship I have for it to make it possible. Here's an example, if you want to follow along: https://studio.webcomponents.dev/edit/1g4cC4DRhZyQhetNqKBf/stories/index.stories.js?p=stories
It's not obvious to me what you intend for this example to illustrate. The point that I was trying to make is that not only does the proposal literally not involve some kind of DOM scope, but that it would theoretically be a bad thing if it did, since such an arbitrary scope would theoretically violate the encapsulation of the owning context. In your example, a component requests slotted content from its owner, which it provides, but part of the content is content that the owner requests from its owner. Again, I don't think this is particularly interesting or surprising; I think it's just the normal behavior of the protocol as proposed working exactly as intended.
However, that protocol is still very much in the ideation phase, and the discussion herein specifically calls out that it, the Context Protocol, and others may be better served by a more generic protocol.
I can't spot the discussion you're referring to in the proposal or the PR. Can you link me to it?
Re: the rest, I think I am fundamentally not understanding what it is you desire. You seem to be saying that a parent scope should be able to coalesce a collection of relevant descendant elements from various descendant DOM trees, but in general it's not apparent to me why we'd want to do that or what it would achieve. The example that I linked does not coalesce a collection of descendants and it's not apparent to me why it would want to. Besides, in the context protocol a provider could identify its consumers by inspecting the received request events to determine the element that dispatched them (and in fact we'll probably want to do this for subscription retargeting in the reworked version of the protocol). Is this the kind of thing you're talking about?
To be clear, my position is just that it's not apparent to me that there actually exists a generic problem here that is relevant outside of the context protocol.
@Westbrook I'm still not sure I quite follow what you were describing, but having thought about this much more, I'm convinced that I was wrong and that there actually is a generic problem here. In fact, I have a use case for such a thing that is separate from context. That said, I think that some kind of contextual protocol is probably the generic solution to this problem, so I'm not sure if this is what you have in mind or not.
I've noticed that various capabilities that are being discussed as protocols involve the resolution of requests inline with an elements DOM scope:
The Context Protocol is broadly used in projects I work with (I work at Adobe, so Photoshop, Illustrator, Express, et al), and some version of Slottable Requests appear to be a quality performance win for those same projects. I've also seen in a similar way the need for collections of DOM elements to be built based on their shared DOM scope (main complexity beyond
el.querySelectorAll(...)
being the elements living in different DOM trees, but the same DOM scope), which if it were a shared issue could likely benefit from a shared protocol, as well. That means, there are probably other DOM Scope relative requests that an overarching resolution protocol could benefit the development of. If that were the case, the Context and Slottable Request Protocols, if they are at some point "Accepted" as official protocols could exist as specializations of this protocol, rather than similar but not the same implementations of DOM scope request resolutions.Some topics that could be more thoroughly investigeted in the context of an overarching protocol:
For now this is more of a stub issue, but in the next few weeks, I plan to dig into this more deeply. Happy to get thoughts on things you'd like to see at that time in the comments below.