WICG / webcomponents

Web Components specifications
Other
4.38k stars 375 forks source link

Style ::slotted children #594

Closed lukasoppermann closed 8 years ago

lukasoppermann commented 8 years ago

Hey,

as it is explained here: https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom#stylinglightdom you can only select a direct item within the slot.

So if this is in our slot

<name-badge>
  <h2>Eric Bidelman</h2>
  <span class="title">
    Digital Jedi, <span class="company">Google</span>
  </span>
</name-badge>

There is no way of styling .company. I understand that the idea is that slotted content may be anything and is added by the user and this should be styled by the user.

However, I am running into issues with this. I am adding stuff to the slot from within my connectedCallback because I am building form elements. This means I need to add a checkbox into the slot so its in the same dom as the form in which the user puts this element.

My desired html would be like this:

<label>
    <input type="checkbox" name="test" style="opacity: 0;" />
    <div class="material-toggle__switch">
        <div class="material-toggle__knob"></div>
    </div>
    <div class="material-toggle__label">${this.innerHTML}</div>
</label>

However I want to style .material-toggle__switch and .material-toggle__knob depending on the :checked state f the input. This is not possible as label is the only element I can reach with ::slotted.

The only solution is to listen for a change event and add an active class to .material-toggle__switch with this html:

          <slot></slot>
          <div class="material-toggle__switch">
              <div class="material-toggle__knob"></div>
          </div>

While it works, it feels very inconvenient. Am I doing it wrong, or is there a way to improve this?

rniwa commented 8 years ago

Hi, I hate to sound patronizing but I just wanted to make sure you know that this is an issue tracker for W3C's web components specifications. We use this issue tracker to modify and update specifications for such features as Shadow DOM and Custom Elements API. Do you have a specific issue with the spec text or a specific feature in shadow DOM? e.g. Are you suggesting that we should add the support for styling descendants of slotted elements?

If you're simply asking what would be the way to achieve what you're trying to do with the existing specifications and with shipping browsers, places like stackoverflow and polymer-dev mailing list would be a more appropriate place to post your question.

lukasoppermann commented 8 years ago

Hey @rniwa, I am aware that this is a specs repo and to my understanding this kind of styling is not supported, which leads to my complicated way of building this. (I just wanted to show a real example, which I think is not so exotic).

I am suggesting that the specs are altered in a way that would allow for all slotted elements to be styled.

Are you suggesting that we should add the support for styling descendants of slotted elements?

Exactly!

rniwa commented 8 years ago

In that case, you could (separately) describe the exact regular/light DOM, shadow DOM, where exactly you're inserting a checkbox, and the desired composed tree? It's unclear from your original description what they're.

lukasoppermann commented 8 years ago

Hey @rniwa,

sure, I will try, my desired setup is like this.

<my-element>
    <slot></slot>
</my-element>

Than I want to insert the following into the slot (needs to be in lightdom, due to the form picking it up):

<label>
    <input type="checkbox" />
    <div class="material-toggle__switch">
        <div class="material-toggle__knob" draggable="true"></div>
    </div>
    <div class="material-toggle__label">Custom Label</div>
</label>

Now I want to be able to do stuff like this within the custom elements <style>:

::slotted(input[checked] ~ .material-toggle__switch){
  /* style the .material-toggle__switch when the checkbox is checked */
}

Does this make sense to you? Do you need any more explanation/info?

hayatoito commented 8 years ago

That is by design. See https://github.com/w3c/webcomponents/issues/331 for details.

We don't have a plan to support an arbitrary selector for ::slotted.

Note that we supported an arbitrary selector in ::content (in Shadow DOM v0) in Blink, as you are suggesting, however, we had experienced that it would make the implementation complicated and would be the root cause of a performance issue. Thus, we introduced this limitation to ::slotted intentionally.

treshugart commented 8 years ago

Note that we supported an arbitrary selector in ::content (in Shadow DOM v0) in Blink, as you are suggesting, however, we had experienced that it would make the implementation complicated and would be the root cause of a performance issue. Thus, we introduced this limitation to ::slotted intentionally.

This also feels like it would break encapsulation a bit too much. For example, if you were able to provide a deep selector for slotted elements, it would touch all descendants of all slotted nodes. Besides the performance implications, this feels like a double standard: nothing can reach in, but the shadow root can reach out.

@lukasoppermann since it's light DOM, couldn't you instead do this from outside the shadow root:

my-element input[checked] ~ .material-toggle__switch

I need to check the spec, but :host instead of my-element might work. I'll update with my finding.

UPDATE

:host doesn't work (I expected that but wasn't 100% sure). That said, the initial suggestion does work: http://codepen.io/treshugart/pen/pEGaVv.

Since this isn't ideal, as it would have to be added globally and for each shadow root my-element is used in, it might be worth making your <label> light DOM into a component that contains the checked styles (you should probably also use :checked instead of [checked]). You mentioned that you can't do this because the checkbox needs to participate in the containing <form> but we've found a workaround: in your component, mutate the host element instead of the shadow root. This makes your component content light DOM, thus able to participate in the containing form.

Maybe something like this pen will work for you?

Happy for you to ping me directly (@treshugart on twitter) since this seems out of scope for this issue thread now.

lukasoppermann commented 8 years ago

Okay, I understand the issue. I just hope for the specs to include a way to pass values to a <form> which will solve all my issues.

@treshugart thanks, I wrote you on twitter, I don't really understand what this means:

in your component, mutate the host element instead of the shadow root. This makes your component content light DOM, thus able to participate in the containing form.

So maybe you can explain it, so I can see if it is a solution for my problem, that would be awesome.

rniwa commented 8 years ago

See https://github.com/w3c/webcomponents/issues/187 for a v2 API to integrate custom elements into forms.

stevenbriscoeca commented 4 years ago

I know this an old thread but i was wondering if it could be reconsidered for this use case.

I have a menu with submenus and I want the html to be like this :

<ds-menu>
      <ul>
        <li><a href="#0">lorem ipsum</a></li>
        <li><a href="#0">lorem ipsum</a></li>
        <li>
             <a href="#0">menu with sub menu</a>
            <ul>
               <li><a href="#0">sub menu</a></li>
            </ul>
       </li>
        <li><a href="#0">lorem ipsum</a></li>
        <li><a href="#0">lorem ipsum</a></li>
      </ul>
</ds-menu>

I want to be able to style the submenu like this :

::slotted(ul ul a) {
  color:red;
}

This is from a frontend framework I am building so I don't have access to the data various teams could be using to build it. So I rather they put the content in the slot (which is SEO friendly with bing), but i cannot access the elements that are nested in.

The only solution I have is to do something like this :

<ds-menu role="list">
        <ds-menuli role="listitem"><a href="#0">lorem ipsum</a></ds-menuli>
        <ds-menuli role="listitem"><a href="#0">lorem ipsum</a></ds-menuli>
        <ds-menuli role="listitem">
             <a href="#0">menu with sub menu</a>
            <ds-menu role="list">
               <ds-menuli role="listitem"><a href="#0">sub menu</a></ds-menuli>
            </ds-menu>
       </ds-menuli>
        <ds-menuli role="listitem"><a href="#0">lorem ipsum</a></ds-menuli>
        <ds-menuli role="listitem"><a href="#0">lorem ipsum</a></ds-menuli>
</ds-menu>

with roles to emulate a list for NVDA, JAWS, Voiceover, etc. Is that solution is considered good practice? I need it to work SEO and be accessible at the same time.

I am sorry if this is not the best place to ask, if anyone can point me in the right direction if this is not the right forum, please do not hesitate.

Ideally I would like to use the html tags that are meant for list and not emulate them with roles.