-
Notifications
You must be signed in to change notification settings - Fork 378
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
Allowing CSS combinators postfixed to the ::slotted() or :host selectors #889
Comments
For those reading email, sorry, I submitted the thread early without any content, and edited the original post. |
What does the |
Wouldn’t |
@castastrophe Thanks for that question! It's not quite what I'm asking about though. The `::slotted` selector runs within a shadow root context (f.e. in a shadow root `querySelector`, or in a shadow root's `<style>` element), but this is already expected behavior. More details (click here to expand)...const root = this.attachShadow(...) // 'this' is a custom element
root.innerHTML = html`
<style>::slotted(.foo) { ... }</style>
<slot></slot>
` The The The custom element itself could in fact run this code internally:
because it will not select nodes from the light tree that have been "slotted" into the shadow root; instead it will select nodes that are within the shadow root's own DOM that have a In other words, calling So inside of a custom element's implementation (in its shadow root), the END DETAILS Anywho, all of the aforementioned is already an existing concept. The main ask of the OP is to be able to take a selector like As an example, try running the following in your console (tested in Chrome). What you'll notice is that the square will be colored The reason is because the class MyEl extends HTMLElement {
connectedCallback() {
const root = this.attachShadow({mode: 'open'})
root.innerHTML = /*html*/ `
<style>
:host { display: block; }
/* This works. */
::slotted(.foo) {
background: deeppink;
}
/* This does not work. */
::slotted(.foo) .bar {
background: cyan;
}
</style>
<slot></slot>
`
}
}
customElements.define('my-el', MyEl)
document.body.insertAdjacentHTML(
'beforeend',
/*html*/ `
<my-el>
<div class="foo">
<div>
<div class="bar"></div>
</div>
</div>
</my-el>
<style>
my-el {}
.foo {
width: 50px;
height: 50px;
}
.foo div {
width: 100%;
height: 100%;
}
</style>
`,
) Expected result: Actual result: |
Ah, thank you for the code example. This is not really about the |
That is not to say that is not a valuable conversation to have - whether or not a component should be able to style more than just top-level nodes inside a slot - but I do think it's a different topic than the title and description here imply. |
I wanted to share this codepen that plays around with a few different ways you can use slotted selectors and pseudo elements: https://codepen.io/castastrophee/pen/OJMKeKa |
Continue the conversation with its full history, that started 5 years ago, and resulted in V1 https://developer.mozilla.org/en-US/docs/Web/HTML/Element/content (deprecated) ( 2015 #331 ) "::slotted" pseudo elements The main take away from 5 years of W3C standards discussions is: I am not the one to disagree with a Components Lead Developer |
Thank you for the links. I am aware of the current spec. Five years is a long time in tech and specs are not immutable. As efficiency is improved, new options may be available. I also was not implying the conversation should be_started_, but pointing out on this thread that it's a different conversation than this one. |
FWIW, if complex slotted light dom selectors were permitted, I'd expect the selector in the examples above to be AFAIK, selectors like this are also not currently permitted, but I'm unsure if that's a matter of deliberate design or not; they still seem to adhere to query-direct-assignees-only. Edit: Thinking about this further, I guess |
If you want to track selectors (in this case |
@castastrophe Did you mean the title I chose doesn't match what I proposed in the OP? If so, in my mind I think it matches because it says "combinators postfixed to the ::slotted() selector", and then I am describing what I believe would be intuitive for that to do. |
@bathos That is not intuitive because it is impossible. Why would someone be thinking that, when it doesn't exist? That's like if I said I think a better interpretation is this:
If we think intuitively about this, then: a selector like Note that the |
@Danny-Engelman, @hayatoito's comment you screenshotted shows no performance metrics. I am not a browser developer, but I very much doubt (I could be wrong) that I wrote a comment about that at #745 (comment). What I mean is, there's plenty of ways to make really slow selectors in regular DOM, without any shadow DOM even existing. We should not throw out an idea based on a single thought that said it would be slow without any viable data. What if I said "multi-pass WebGL rendering is slower than single-pass rendering, so we should throw out multi-pass APIs". But in fact, multi-pass rendering can be very useful when the performance implications fit within given constraints. It would be great to give web developers useful selectors, and then explain to them that they should avoid re-running these selectors repeatedly; but that it is fine if the cost fits within performance requirements for the application. I feel that we're prematurely optimizing here. However I know that the web APIs can't be reversed (though I've been imagining how to do that without breaking the web). Every single DOM API that exists can technically be slooow if we examine it within the context of a particular use case that happens to be the worst use case where we'd never want to perform the given action in the way it is performed. The following would not be ideal (if selectors like
|
The performance issue is that it increments the amount of subtrees in which every node needs to go look for rules that affect to them. Right now the logic goes like: if you're slotted, traverse your slots and collect rules in their shadow trees as needed. This is the code fwiw. This is nice because the complexity of styling the element depends directly on the complexity of the shadow trees that you're building, and it only affects slotted nodes. If you want to allow combinators past slotted then every node would need to look at its ancestor and prev-sibling chain and look at which ones of them are slotted, then do that process for all their slots. Then, on top, you also need to change the general selector-matching code so that selectors that do not contain slotted selectors don't match if you're not in the right shadow tree. That's a cost that you pay for all elements, regardless of whether you use Shadow DOM or |
I'm just not sure that referencing slotted content inside the ShadowRoot is possible or makes logical sense if we consider the main benefit of web components: scope. The ShadowRoot does not know what it's slotted content looks like, only that it has slots. The slots point to the light DOM. If you want to capture the light DOM on a component, you use: tldr; Scope is the primary discussion here imo. A component is tightly scoped to see it's own template and it's top-level nodes assigned to a slot, no deeper and no higher. That scope is a powerful tool that can be leveraged. |
@calebdwilliams I think for the dialog usecase named slots are a reasonable solution. You then could do Introducing a particular part-like attribute definitely mitigates the "now all elements need to look at all their ancestors for slots", for sure (at the cost of adding one more part-like attribute, which is also a bit of an annoyance because it involves one extra branch in all attribute mutations, but probably not a huge issue). That being said, allowing arbitrary access to the slotted DOM is a bit fishy. That way you start depending on the shape of your slotted DOM tree and that reintroduces the same issue that you're trying to solve with shadow DOM in the first place, which is making an isolated, reusable component. I think that's the point that @catastrophe is making, which I agree with. |
@emilio, yeah, I wrestled with that but I keep coming back to the idea that the slotted content is not (or should not be) necessarily required. A dialog without a cancel button is potentially fine. Granted that might not be how people use the feature. |
That's fine? You can have an empty slot. Anyway that's a bit off-topic anyhow. |
@emilio In some ways, I feel you both on that sentiment. But there are other possibilities too. The idea is that we allow custom element authors to be more inventive by giving them flexibility. For example, a custom element author may describe certain usage requirements in the component documentation, and it could require a user to nest elements like follows, where the <foo-interesting-layout>
<div slot="left">
... any other stuff ...
<foo-close></foo-close>
... any other stuff ...
</div>
<div slot="center">
... any other stuff ...
<foo-open-left></foo-open-left>
... any other stuff ...
<foo-open-right></foo-open-right>
... any other stuff ...
</div>
<div slot="right">
... any other stuff ...
<foo-close></foo-close>
... any other stuff ...
</div>
</foo-interesting-layout> Now, the In my mind, this sort of nesting is a totally valid thing that a library author could document as a requirement, and therefore should have some easy way to perform the styling. The most important thing to note is that performing the styling is entirely possible today, the feature I ask for only makes it easier with less code. The way we can do it today is the library author places a If the author was able to use As with many features of a language or API, there's wrong ways to do just about anything, but I do believe this feature would give authors easier inventiveness without (for example) having to track root nodes and ensure that they don't have duplicate If the @emilio @catastrophe If the custom element author relies on certain slotted DOM structure without documenting that, of course that's bad. It isn't to say a custom element author can't make good documentation to describe what an end user should do. Secondly, without any documentation, the end user will have a hard time guessing what the structure should be anyway, so they probably wouldn't even bother to use that custom element. People generally don't like to guess how an API works. So that point, though fully valid, doesn't have as high of a significance (in my humble opinion) as the proposed feature does, in that the proposed feature would allow CE authors to achieve things more easily (and usage of those things should be documented for end users). |
@emilio For that particular use case, where the slotted thing is a simple A CE author could be fairly strict with the requirements, f.e. the author could document the strict requirements and be using selectors like One feature in particular that relies on lose structure is CSS transforms. CSS Imagine the 3D possibilities: imagine how a custom element wrapping a DOM tree, relying on selectors post-fixed to (I'm working on this at http://lume.io, but I have a lot left to do... F.e. here's WebGL-enhanced There are many possibilities. What we've just imagined is doable today, but |
I think, at least slotted parts should be supported by minimum: |
@OnurGumus From reading that, I'm not sure what is being proposed there or how that's an alternative to the OP. Could you provide an example? Is it to say that the light DOM author could specify parts, then the Custom Element author (or ShadowDOM author) could style those If that's what is meant (the light DOM author specifies stylable For example, |
@trusktr What we know is slotted is limited to the "public surface" of the component for performance reasons. OP argues that we shouldn't have that limitation whereas probably some people would reject that. |
The to make sure we're on the same page, the OP is not talking about exposing parts of a component to the user, the OP is talking about the component being able to more fully work with the tree that the user passed into the component as a distributed/slotted tree; to be able to more easily access that tree in style context. Note: I know we can already access the light tree, and style all of its elements. This is simply proposing something more ergonomic. In React, for example, this is easy to do. The component author simply iterates over the array This easy flexibility is what the OP is asking for, but for Web Components, and in this case the The main thing to note, is that Web Component authors can already access all light DOM and style the elements any way they wish. The OP only aims to make it easy to implement for WC authors. implementation example using current available APIs:
So you see, what the OP asks for is totally possible today. The OP merely asks for a concise and simple syntax option. |
What a disappointing arbitrary limitation. Let the browser-makers solve performance issues and write the spec to be ideal. WEAK! I want complete control over the styling of slots and all their children. I wish there was a flag we could flip so that slots would ignore non-shadow styling. A slot is really just a really ugly argument to a function. If you pass something into my function (custom element) I should be able to do what ever I want to that input and all its children too. The whole goal, for me, is to have every aspect of my web component contained in one file and I don't like having to fool with document level CSS to style children of shadow slotted element. I can still do what I'm wanting with hacks and tricks, but allowing shadow level CSS to style children of slotted elements would make the whole experience much nicer. What @trusktr has been saying is ideal to me. Make the spec ideal and let the browsers-makers earn their money. They're smart enough to overcome the performance issues, so don't be so easy on them in the spec. This stuff is for the whole world. Let's impress the aliens when they arrive. Their web standards already do this! |
There is no fool with document level CSS issue, the only limitation is:
Then the browser-makers can focus on creating something that really impresses aliens... This is becoming an "I think Array index should start at 1" topic 🖖 |
Since Lonnie Best challenged me, here is my workaround to style all slotted content without using Code at: https://jsfiddle.net/CustomElementsExamples/Lhcsd2m5/ Code at: https://jsfiddle.net/CustomElementsExamples/Lhcsd2m5/ customElements.define('to-do', class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure we can access (light)DOM here
let template = document.getElementById(this.nodeName).content.cloneNode(true);
let div = this.attachShadow({ // create TO-DO with shadowDOM and DIV in it
mode: 'open'
}).appendChild(document.createElement("div"));
div.attachShadow({ // attach shadowDOM to DIV container
mode: 'open'
}).append(template); /* add Template content to DIV shadowRoot */
div.append( // move to DIV lightDOM:
div.shadowRoot.querySelector('#styleslots'), // style originally from Template
...this.children // all original to-do lightDOM (this required the setTimeout!)
);
})
}
}); |
Cool. So basically what you are saying is (You could've simply just said that.) A hypothetical Works great! For some reason I hadn't thought of it. Side note: performance of the hypothetical |
@Danny-Engelman Can you make a really simple example, not a super complex one? I am not having any luck with it: https://codepen.io/trusktr/pen/e80e9d5d09de7facf22aeee85f6a7549 As you can see there, only the Shadow DOM's nodes are styled, but not the content that is "slotted".
Not sure why I thought it "worked great" before, but clearly in that example it doesn't work. Update: I thought I could make it work with a combination of slot > p > span,
:host > p > span { ... } https://codepen.io/trusktr/pen/cf1ee41ddf779a7fb60eff121b65bc1c It seems CSS combinators are not allowed after |
From what I can tell, this is a "There's no way to index into an array with simple notation" problem. 😄 Here's a solution that works: // within the custom element code, inside a MutationObserver handler:
this.children.forEach(child => {
if (child instanceof HTMLParagraphElement && child.children[0] instanceof HTMLSpanElement) {
child.children[0].style = 'background: cyan'
}
}) but that's like a
sort of topic, except the CSS version is uglier because it modifies the end user's public |
No, I am not. It is not possible. Slotted content is styled by its host container (global CSS if you only have 1 one element with shadowDOM)
There is no simple example. Your CodePen has one shadowRoot.
ps: your CodePen is private so can't be forked I keep the ::slotted StackOverflow answer, I posted a year ago, updated with every fact and rumor I read. no updates there for months. |
Joe Pea aka trusktr, got me working again. It is still a Work-Around!! To style I rewrote the code.. JS is still the same, except I changed I deleted most styling examples and comments. Is this simple enough? TWO shadowRoots to style |
Ah, ok. I see what you did, which I missed before. It is a very bad solution because it totally obliterates the end user's tree (removes it from their DOM, and places it into an internal shadow root). This has critical issues:
I would suggest for anyone reading this, never ever write code that way. That is simply not an acceptable solution because the downsides (especially for the end user) are bigger than the benefit (namely for the CE author only). @Danny-Engelman Unless there is some solution we've missed, you shown why clearly we need a solution, so as to avoid complicated and highly undesirable workarounds. The most acceptable solution that I can think of right now is, to implement scoped styling:
This approach would be much less invasive to the end user's API contract (the end user's input (light tree nodes and attributes) should ideally be left in-tact and undisturbed, because that is theirs to manipulate). The end user will most likely be unaffected by the There is no simple solution to this, as far as I can tell so far (apart from having a feature like in the OP). |
See also w3c/csswg-drafts#3896 |
This is needed |
Related: #936 |
Borrowing some ideas from #883, I think it would be great to allow combinators after
::slotted(whatever)
, for example::slotted(.foo) .bar > .lorem + ip-sum
. Similarly, the previous selector would be effectively the same as:host > .foo .bar > .lorem + ip-sum
for styling purposes, but:host
also does not allow combinators to be appended to it.Here's a live example showing that it currently doesn't work:
https://stackoverflow.com/questions/62842418
On the same token, being able to use these selectors in
querySelector
andquerySelectorAll
would be very convenient.Here's a live example that shows that
querySelector('::slotted(.foo)')
doesn't work despite being a valid selector, and also showing that postfixed combinators throw an error:https://codepen.io/trusktr/pen/d4a45f1efc9eccc0fdb20164566bada4?editors=1010
Having these features as expansions on the current spec would allow what the OP in #883 asks for: it would make it very easy to style the input tree that a custom element end user provides.
There are currently complicated ways to achieve this (see comments below).
The text was updated successfully, but these errors were encountered: