-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Default element type(s) for <slot>? #6051
Comments
Note that #5483 allows the desired behavior to be accomplished in JavaScript. /cc @yuzhe-han; it would be good to get that PR finished and merged. |
For context: selectors has been considered but not done due to performance concerns. Element local name matching is feasible, but it's unclear how often it would be used in practice. If you write complicated components you probably quickly reach the limit of how useful that is. Having an API is probably a good first step here. |
Thanks for the context @annevk. Note that selector matching is a spectrum. Would it help alleviate the performance concerns to only allow compound selectors? There is precedent of web platform features only allowing compound selectors for performance reasons (certain functional pseudo-classes) . |
@LeaVerou ah, that might be interesting to consider. cc @whatwg/components |
Compound selectors is exactly what v0 shadow DOM API used, and it was highly undesirable performance characteristics. As such, we wouldn't consider them. |
There might be, however, an argument to be made about the default slot name for a custom element like we have the default ARIA role for custom elements. If we also added the capability to just select a single element to a given slot, that would basically achieve implementing something like |
(I forgot that, but indeed: https://www.w3.org/TR/2014/WD-shadow-dom-20140617/#dfn-content-element-select.) |
@rniwa That does address a majority of use cases, which are assigning a default slot to a custom element, but it does not address use cases where one wants to set a default slot for a specific type of built-in element. E.g. it does not allow implementing something like |
@rniwa what would be the lift of getting more up-to-date performance characteristics on this sort of approach? v0 Shadow DOM and associated learnings are 6+ years old now, and the availability of advanced CSS selectors has grown at an incredible pace in the interim. I can certainly see the perf still not being up to snuff, but I'd be interested to see if we'd at least gotten any closer in this area. Additionally, modern perf data on this would be helpful in clarifying not only this conversation, but others like w3c/webcomponents-cg#5 (comment) as well. |
I've started to think that perhaps the right approach is to decorate each custom element with a "brand", and then slot to get assignment based on that "brand". Note the fact there are more advanced CSS selectors will only make it less likely that CSS selectors will be an acceptable way of assigning nodes to slots. To recap, there are two primary ways using CSS selectors will pose a serious perf issue:
With name based slot assignment, both of operations above will complete in O(1) in terms of determining whether a given node will be assigned to a given slot if there is exactly one slot of a given name (or default slot). This is critically important in order to support |
I’ve been using a pattern in most scenarios which seems aligned with what you’re describing, if I understand right — I have a helper that accepts config like One of the reasons I do not use CSS selectors for this currently is that I consider the HTML part of “HTML foo-bar” elements important. Don’t want no eldritch void beasts entering my peaceful herd of foobs: |
An API for more complicated cases is definitely a must, but I think a lot of simple cases could be handled by a slot taking a single tag name that is only matched against top level children of the custom element. That matches how a lot of standard html elements work and seems like a good 80% solution to me. Something like |
@rniwa Would a (comma-separated) list of tag names work? That is compatible with selectors and can be extended in the future, would address the bulk of use cases, and seems like it could be nearly O(1) time on the number of slots and nodes if you make a map of tag names to slots that target these tag names. @jimmyfrasche |
Using a single element name is something I had suggested in the past but Google opposed to it / not supportive of the idea at the time. It's possible their opinions have changed since then. However, given one of the reasons @JanMiksovsky cited of liking slots better than CSS selector was that it allows subclassing to work, I'm not sure adding this specific capability makes a lot of sense especially since the idea of branding / slot kind to a custom element will work with subclassing. Note that an important use case here is to let users of a component subclass it so it's not sufficient to enumerate the list of element names in a slot. In addition, it's not backwards compatible to add the support for CSS selector syntax after shipping element name matching mechanism using the same content attribute, and adding any kind of forward compatibility parsing support for this purpose will significant performance cost of element name matching feature. I'm also going to reiterate Apple's WebKit team's position that simple (or other subset of) CSS selectors as a mechanism to assign nodes to a slot is not something we ever want to support. And we're saying this as the only browser engine vendor that has implemented and ships CSS JIT.
That's what happens with named slots so we most definitely we should just pick the first one. But this still poses a perf challenge if a single slot can match against multiple element names because then we'd have to match against M element names for K slots, which will be O(KM). It's also problematic that a new element name can be added / removed from a given slot. This is akin to removing the same number of slots as there are element names specified for a given slot. It's actually worse because slots can have many to many dependencies between them. It would inevitably worsen either memory or runtime complexity or both. |
@rniwa what might the branding “keys” look like? Would it be possible for there to be keys available for native elements and for non-element node types? I.E., it seems clear that we’d be able to express “this slot wants Something I’d forgotten also is that in several of these cases for me, there’s a “catch-all” slot, one which should receive everything not otherwise mapped to a specific slot, like how nameless (AFAICT, all of these things could theoretically be addressed within a branding API, I’m just having trouble picturing what it would look like.) |
Maybe. In order to design what this mechanism should work, we need to enumerate a list of concrete use cases for which this mechanism will be used. We may find that in practice we'd need script-based imperative slotting anyway in which case adding this API won't be necessary. Or we might find that many use cases need an entirely different mechanism. None of that will be clear until we understand the set of use cases we're trying to address.
What are motivating use cases for this? It's impossible to assign text nodes to any but the default slot already. What are concrete use cases in which this will come up?
What are those cases? Again, it's important to explicitly enumerate concrete use cases. It's impossible to judge whatever solution / proposal we come up without having use cases to evaluate against. |
I wasn’t aware of this because Chrome’s current implementation does allow assigning text nodes.
For a
In most cases, the “head” part of the tree item would be text content, but could include other markup, including potentially interactive markup (e.g. in a file explorer, a tree item label may be editable). Similar scenarios can arise with other “recursive” accessible widgets like menus. HTML’s native |
You mean with imperative API? There is no way to specify a slot name on a text node.
Sounds like either the "head" part can be inside an element of its own or a default slot can be used?
What does this mean? It's unclear why existing name-based slotting doesn't work in these cases. |
Yes — I’m currently able to do
We could. We wouldn’t because of the noise it would create for the common case. It’s almost always — but not always — a single text node.
Apart from providing assurances about element-of-type slot assignees (e.g. for unguarded #private member access, assuming they don’t qualify for assignment till upgrade), name-based slotting can already address all use cases as far as I’m aware. This thread seems to concern its DX shortcomings, but DX shortcomings are subjective. I don’t think it’d be possible for me to present a use case where it isn’t possible to say “but you could have a different API that uses named slots,” right? FWIW, we’re able to address this with fully-imperative manual assignment. It’s working well. We would switch to declared-types assignment if possible because it’d be simpler and presumably more efficient. In some cases, I suspect that switch will be possible no matter what the typed-slots API looks like. In other cases, the switch is only possible if there’s a way to have typed-slots + a default slot. If the latter isn’t in the cards, though, it’s not the end of the world, and perhaps I’m mistaken and other folks aren’t expecting to be able to do that. Edit: I realized reviewing the last few comments that there’s probably some “drift” in here and that although I asked about cases like “this slot wants text”, I am really only concerned currently with the possibility of still having a default slot, in the same style as named-slots mode, if using typed-slots mode. When I answered about use cases, I was thinking about that, having forgotten that my question was about a more specific capability. This was probably confusing, sorry! |
Ah, okay, yes, the default slot is still possible with "brand" based slot assignments. |
@rniwa I'm not really sure what brand based slot assignments entail so it's hard to evaluate. Is there something you can link me to? |
I'm not proposing anything. Imagine you can specify "kind" or "brand" for a given custom element and let that inherit transparently to their subclasses. Then you assign nodes to a slot based on this "kind" or "brand". This solves two problems:
|
I doubt I'd ever do anything with subclasses but that seems like it would solve the common case just as well as specifying the tag name for a slot and it would be transparent to the consumer so either works for me. |
Am I correct to imagine something like this would be one possible realization of such an API?: let brand = customElements.createBrand();
customElements.define('x-y', class extends HTMLElement {
constructor() {
super();
brand.register(this);
}
}); This is not an API suggestion — I’m just looking to confirm if I understand the model being referred to as brand-based. It seems like it would need to be a WeakMap-ish thing with an imperative invocation step during construction if it is to be subclassing friendly. Is that accurate? |
The way I'm envisioning, it would be more like this:
or
|
Ah, interesting, that is certainly very different from what I thought that term meant. This would actually not usable for us if it is a forgeable status. |
Why is that? Again, it's very important for us to understand the set of use cases we're trying to address. |
@rniwa Aiming for robust and reliable internal behavior (near-)guarantees even under unusual circumstances. Standard disclaimer that I’m aware there are hard limits on how much and what kinds of soundness can be achieved in any API authored in the JS layer (this is the primary problem space I study). Since I realize this is a very niche requirement, I don’t expect it to inform the API design decisions, at least not if doing so would have a negative impact on how the API addresses much more common needs like subclassing. |
I guess we can use a
|
I guess we can also use a getter/setter attribute like |
Does that mean that you need to set the brand of a slot using js too? I figured it was going to be a brand=name attr |
In the case of string-based "brand" name, that should work fine. |
whoa nice — i love that, one contract transparently facilitating both the closed-over and open usage patterns |
Currently, if there are more than one slot, components need to instruct authors to place the right
slot="name"
attributes in their light DOM elements, even when those are of very specific types.For example, take a look at Shoelace tabs:
Only a
<sl-tab>
element will ever be in anav
slot, yetslot="nav"
needs to be specified, making the markup more repetitive and error prone. Same with Elix tabs:This seems to be a rather common issue with any WC with nontrivial lightDOM contents. Theoretically the component can assign any missing slots on these elements by adding a
slot
attribute, but this goes against certain guidelines that instruct component authors to not add attributes on author-provided light DOM elements.It would be good if the component author could specify a default element type for each slot, a list of types or even a CSS selector, possibly via an attribute on
<slot>
, so that these redundantslot
attributes can be eliminated, and only used when the slot is non-obvious. Note that expanding the syntax could be done gradually: The attribute could accept an element name or comma-separated list of names at first, and potentially evolve into accepting CSS selectors if use cases arise with no backwards compat issues, since these have the same meaning as selectors too.The text was updated successfully, but these errors were encountered: