- From: Joe Pea <notifications@github.com>
- Date: Sun, 12 Jul 2020 13:18:47 -0700
- To: w3c/webcomponents <webcomponents@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <w3c/webcomponents/issues/883/657270047@github.com>
@Danny-Engelman Thanks for the thoughts!
> `::slotted(.foo) + P` will never work because: **There is no `<P>` -IN- shadowDOM**
That selector would do what it says verbatim: select the sibling `<p>` element of the element targeted by `::slotted(.foo)`. The sibling `<p>` element (if any) would be in the same light tree as the element targeted by `::slotted(.foo)`. I wouldn't expect it to select anything in the shadow DOM as it wouldn't make intuitive sense. (I could imagine a `::shadow-sibling()` selector for that.)
If the sibling `<p>` element is not distributed and therefore not visible, there's no styling applied to it (intuitively). However if the author of a custom element adds a `<slot>` element later (f.e. due to some user interaction or other feature) and the sibling `<p>` element thus becomes distributed and therefore visible, then the styling would apply.
It would also be convenient for getting references: `root.querySelector('::slotted(.foo) + p')`
The code for `querySelector('::slotted(*) span')` roughly equates to this:
```js
const d = document.querySelector('div')
const r = d.attachShadow({mode: 'open'})
r.innerHTML = `
<div>
<slot></slot>
</div>
`
const start = performance.now()
// Emulate querySelector('::slotted(*) span')
const slots = r.querySelectorAll('slot')
const queryResult = []
// outer loops gets result for `::slotted(*)`
for (let i = 0, l = slots.length, slot, elems; i < l; i += 1) {
slot = slots[i]
elems = slot.assignedElements()
// inner loop gets result for ` span`
for (let j = 0, l = elems.length; j < l; j += 1) {
queryResult.push(...elems[j].querySelectorAll('span'))
}
}
const end = performance.now()
const total = end - start
console.log(`The query took ${total} milliseconds.`)
console.log(...queryResult)
```
and similar for polyfilling other queries.
Putting it this way, it is easy to imagine how `querySelector('::slotted(*) + p')` would work, by replacing
```js
queryResult.push(...elems[j].querySelectorAll('.bar'))
```
with
```js
if (elems[j].nextElementSibling?.tagName === 'P')
queryResult.push(elems[j].nextElementSibling)
```
> A reason why `::slotted` became a simple selector is performance
The performance claims are speculative. :thinking: As with any selector, users should save the results to variables rather than executing them over and over (f.e. in an animation).
> _**I presume**_ that makes a more advanced `::slotted` selector a performance issue because content can now come from anywhere in the AST, not just a sub-tree
I'm not sure what you mean by "anywhere in the AST, not just a sub-tree", but what I propose is that the postfix combinators run in the light tree (which to me intuitively makes sense).
What I've proposed actually doesn't change the performance of `::slotted(whatever)`. In the query `::slotted(whatever) > ul li.bar` the performance of `::slotted(whatever)` is exactly the same as it is today. Then the performance of ` > ul li.bar` (which is an existing feature today) would be the same as if you tacked that onto the result of any other non-slotted selector.
---
Here's a live example of the above code emulating `querySelector('::slotted(*) span')`:
https://codepen.io/trusktr/pen/0a71670d0da693a4758bc4f36463452f?editors=1010
On my computer the output is `The query took 0.005993600000097104 milliseconds on average.`. It ranged from 0.005 to 0.15 when I tried it many times. Probably I had other things going on during the slower ones.
I really don't think there's a performance issue here that is any different than all other existing selectors. I don't think we can really use this claim against allowing combinators postfixed to `::slotted(whatever)`.
I believe that performance claim is a moot point because we can easily make selectors that will perform a lot worse than `::slotted(whatever) > ul li.bar` using the selector features of today.
---
> ```
> <style>
> /* This correctly styles the reflected (slotted) content: */
> p span {
> border: 1px solid deeppink;
> }
> </style>
> ```
There's a problem with this: this is how a _user styling_ can be applied to content that is passed to a custom element. This does not allow a _custom element author_ to perform the styling.
Allowing custom element authors to perform the styling of the light tree (without users having to think about it) is a valid use case and makes a variety of things possible (f.e. user provides markup and the output looks good without the user also having to stick additional styles or stylesheets outside of the custom element).
---
> Key here is slotted content is **not a copy**, and **not the original**, but a **reflection**
I think teaching people using the standard spec terminology is the best way to go, so that the concepts match up across articles and guides.
> (with the limitation only one reflection is possible)
But the terminology that you use really has no bearing on the topic; it only describes what currently exists. If the spec were expanded to allow the combinators postfixed after `::slotted` selectors, then you would simply update your custom language to reflect the new reality (no pun intended! :smile:).
---
On a similar note, `querySelector('::slotted(.foo)')` doesn't even work although it is a valid selector. Demo:
https://codepen.io/trusktr/pen/d4a45f1efc9eccc0fdb20164566bada4?editors=1010 (shows `null` in console)
---
In my opinion there's really not a good reason why a custom element author should not be able to style deeper elements within slotted elements (and also select them with `querySelector`).
--
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/883#issuecomment-657270047
Received on Sunday, 12 July 2020 20:19:01 UTC