[css-pseudo][css-shadow-parts] Define "part-like pseudo-element" concept · Issue #10083 · w3c/csswg-drafts (original) (raw)
In the WHATNOT joint session with CSSWG today, we resolved that the new details
pseudos would indeed be exposed as normal UA pseudo-elements (rather than ::part()
s), but that we'd define a new concept of "part-like pseudo-elements" that grants the pseudo the abilities of ::part() that we want to preserve.
Namely:
- a part-like pseudo has the ::part() blocklist of a few pseudo-classes (the tree-structural ones) and allows all other pseudo-elements and pseudo-classes, rather than the normal pseudo _allow_list of a few pseudo-classes (the logical-combination and user-action ones) and banning all the rest.
- a part-like pseudo has a well-defined inheritance relationship with the originating element and other part-like pseudos, rather than the normal "always inherit from the originating element" behavior.
- the
exportparts
attribute will gain a syntax for exporting part-like pseudos, details TBD - we categorize future pseudo-elements as "part-like" when appropriate, and do a review of existing pseudos for acceptability of retroactively labeling them "part-like" and giving them these new powers
IIRC, the only real point of contention left is that, for normal parts, you only have access to the parts declared or exported by the component itself. If you expose a sub-component as a part, you do not get access to its parts too (that is, you can't ever write my-el::part(my-foo)::part(their-bar)
), specifically so we don't expose the fact that the part is implemented as a nested component rather than plain DOM. But parts do provide access to their pseudo-elements (you can write my-el::part(my-foo)::before
), because that's useful and many pseudo-elements aren't remotely appropriate to be exported as parts themselves.
But these two points are now in conflict. If you can export a part-like pseudo to be one of your component's parts, but you can also access it as a pseudo-element, it means you can spell the same selector two distinct ways: my-el::part(my-content)
and my-el::part(my-details)::details-content
.
This does expose the fact that a part is a particular built-in element, but that's a fact we already explicitly exposed, so it's not a new information leak. (Any element-specific pseudo-classes also expose this.)
It also means there's two ways to refer to the same element, but that's generally true of selectors, so I don't find this a particularly compelling problem.
I suggest, then, that we simply accept this as a possibility. But we could try to work around it in a few ways:
- Block access to part-like pseudos from a part. But this wouldn't be retro-compatible if someone was already doing, say,
my-el::part(file)::file-selector-button
, and it requires people to be very aware of the part-like pseudos exposed by any particular element. - Block access to part-like pseudos from a part if they were exported. So you could write
my-el::part(my-details)::details-contents
ormy-el::part(my-contents)
for a given element, but never both. This potentially allows for inconsistent behavior, where some pseudos are allowed (because they weren't exported under another name) but others aren't. - Block access to all the part-like pseudos from a part if any were exported. Same as (2), but now you're at least guaranteed that all the part-like pseudos are consistently shown or consistently hidden, and there's no back-compat issues like (1). But it still means that _non_-part-like pseudos are exposed on the part, along with any element-specific pseudo-classes, so you're still not meaningfully hiding the implementation details. And if we add more part-like pseudos to an existing element, they'd be blocked by default if a component exposed any of them, until the component was updated.
I think all three of these have downsides, and personally consider them to outweigh the downside of just "you can access it in two different ways". So I recommend not doing anything special for this.