::slotted CSS selector for nested children

て烟熏妆下的殇ゞ 提交于 2020-08-05 05:57:19

问题


The CSS ::slotted selector selects children of the <slot> element.

however, when trying to select grandchildren like with ::slotted(*), ::slotted(*) *, or ::slotted(* *), the selector doesn't seem to take effect.

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'open'})
    shadowRoot.innerHTML = `
      <style>
        ::slotted(*) {
          display: block;
          border: solid blue 1px;
          padding: 3px;
        }
        ::slotted(*) span {
          display: block;
          border: solid red 1px;
          padding: 3px;
        }
        ::slotted(* span) {
          display: block;
          border: solid green 1px;
          padding: 3px;
        }
      </style>
      <slot></slot>
    `;
  }
}
customElements.define('my-element', MyElement);
<my-element>
  <p>
    <span>Test</span>
  </p>
</my-element>

Note how the span doesn't get the border.

Is this expected behavior? I wasn't able to find concrete documentation for this.

If yes, is there a way to work around this?


回答1:


TL;DR

  • slotted content remains in lightDOM, is reflected to a <slot>

  • ::slotted(*) can only target the lightDOM SKIN with simple selectors


Yes, ::slotted() not styling nested elements is expected behavior.

background

The term slotted is counterintuitive,
it implies element lightDOM is moved to shadowDOM

slotted lightDOM is NOT moved, it remains.. hidden.. in lightDOM
the content is reflected to a <slot></slot>

Or from Google Developer Documentation

Conceptually, distributed nodes can seem a bit bizarre.
Slots don't physically move DOM; they render it at another location inside the shadow DOM.

I use the term reflected instead of render because render implies you can access it in shadowDOM. You can not, because slotted content isn't in shadowDOM... only reflected from lightDOM.


Why :slotted has limited functionality

The main takeway from the W3C standards discussions (@hayatoito here and here) is:

https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted

So due to performance issues

:slotted( S ) got limited CSS selector functionality:

  • ► it only takes simple selectors for S. --> Basically anything with a space won't work

  • ► it only targets lightDOM 'skin'. --> In other words, only the first level

    • ::slotted(h1) and ::slotted(p) works

    • ::slotted(.foo) works

    • ::slotted(span) (or anything deeper) will not work (not a 'skin' element)

<my-element>
  <h1>Hello World</h1> 
  <p class=foo>
    <span>....</span>
  </p>
  <p class=bar>
    <span>....</span>
  </p>
</my-element>

workarounds

workaround #1 - style lightDOM

The <span> is hidden in lightDOM, any changes made there will continue to reflect to its slotted representation.

That means you can apply any styling you want with CSS in the main DOM
(or a parent shadowDOM container if you wrapped <my-element> in one)

 <style>
  my-element span {
    .. any CSS you want
  }
 <style>

Note: ::slotted([Simple Selector]) confirms to Specificity rules,
but (being simple) does not add weight to lightDOM skin selectors, so never gets higher Specificity.
You might need !important in some (rare) use cases.

 <style>
  ::slotted(H1) {
    color: blue !important;
  }
 <style>

workaround #2 - move lightDOM to shadowDOM

If you move lightDOM with appendChild or append (or insertBefore or cloneNode, etc.) from lightDOM to shadowDOM, you can do all styling you want.

Without using <slot></slot> and :slotted()

workaround #3 - ::part (shadow Parts)

(maybe not call it a workaround) It is a different/powerful way of styling shadowDOM content:

Apple finally implemented this in Safari 13.1, March 2020

see:

  • https://css-tricks.com/styling-in-the-shadow-dom-with-css-shadow-parts/

  • https://dev.to/webpadawan/css-shadow-parts-are-coming-mi5

Note! ::part styles shadowDOM, <slot></slot> content remains in lightDOM!


references

be aware: might contain v0 documentation!

  • https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=en#composition_slot

  • https://polymer-library.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-your-elements

  • https://github.com/w3c/webcomponents/issues/331

  • https://github.com/w3c/webcomponents/issues/745

  • https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/slotchange_event

  • ::part() - https://developer.mozilla.org/en-US/docs/Web/CSS/::part


Related SO answers: WCSLOT


Using slots as a router

Change the slot-name on buttonclick and pull in new content from lightDOM:

<style>
  img { /* style all IMGs in lightDOM */
    max-height: 70vh;
    border:2px solid green;
  }
</style>
<my-element>
  <span slot=answer>SLOTs are:<button>Cool</button><button>Awesome</button><button>Great</button></span>
  <div  slot=Cool>   <img src="https://i.imgur.com/VUOujQT.jpg"></div>
  <span slot=Awesome><h3>SUPER!</h3></span>
  <div  slot=Awesome><img src="https://i.imgur.com/y95Jq5x.jpg"></div>
  <div  slot=Great>  <img src="https://i.imgur.com/gUFZNQH.jpg"></div>
</my-element>

<script>
  customElements.define('my-element', class extends HTMLElement {
    connectedCallback() {
      this.attachShadow({mode: 'open'})
          .innerHTML = `<slot name=answer></slot>`;
      this.onclick = (evt) => {
           const label = evt.composedPath()[0].innerText; // button: Cool,Awesome,Great
           this.shadowRoot.querySelector('slot').name = label;
           this.children[0].slot = label;//include lightDOM answer buttons again
      }
    }
  });
</script>


来源:https://stackoverflow.com/questions/61626493/slotted-css-selector-for-nested-children

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!