Re: [w3c/webcomponents] idea: Allow light DOM to delegate styles/behavior to shadow DOM (#883)

@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