Re: [webcomponents]: The Shadow Cat in the Hat Edition

On Mon, Sep 9, 2013 at 4:32 PM, Dimitri Glazkov <dglazkov@google.com> wrote:
> Part 1: Revenge of the :host
>
> Turns out, it's bad to be Super Man. After the Shadow DOM meetup,
> where we decided that shadow host could be matched by both outer and
> inner trees (http://lists.w3.org/Archives/Public/public-webapps/2013AprJun/0985.html,
> first point), we quickly coded this up in Blink and gave it to Mikey
> ...erm, the Polymer folks to chew on.
>
> The folks spat out that morsel right across the table
> (https://www.w3.org/Bugs/Public/show_bug.cgi?id=22980), and presented
> good arguments to justify their etiquette faux pas.
>
> For what it's worth, it would be fairly easy to make shadow tree rules
> match shadow host only when ":host" is present in a rule.
>
> Unfortunately, this would leave Tab (and other CSS WG folks) in a sad
> state, since addressing these arguments makes it harder to keep a
> straight face with the concept of a pseudo class in regard to :host.
> See discussion on bug for the gory details.
>
> As of now, we are in that angsty state of not knowing what to do next.
> Any ideas are appreciated. Note that there are some well-established
> concepts in CSS and inventing fewer new concepts is much preferred.
> Reuse, reduce, recycle.

Some in-person discussion led to an elegant solution:  keep the host
element in the set of elements, but neuter all of its features. As far
as the selectors in the shadow root can see, it's a node with no
tagname, no id, no classes, etc.  The one and only thing that will
match it is :host and :host().  (And *, of course.)

Also, just in case it wasn't clear earlier, :host() only matches the
host element.  Like :drop/:drop(), the non-function version is just
the "widest possible" version of the functional version - it always
matches the host, while ":host(sel)" only matches the host if
something in the ancestor tree matches "sel".

Here's an example:

<div class="foo">
  <x-dialog>
    [shadow-root]
      <div class="bar">some text!</div>
      <style>
        div { ... } // matches the div.bar
        :host { ... } // matches the x-dialog
        x-dialog { ... } // matches nothing...
        // there's no node named "x-dialog" viewable from here
      </style>
  </x-dialog>
</div>

> Part 2: Party ::part part
>
> Another possible wrinkle is the ::part pseudo element. After also
> chewing on ::part for a little while, our brave guinea pi.. erm,
> people also declared it to be tasting somewhat bitter.
>
> The best symptom can be seen here:
> https://www.w3.org/Bugs/Public/show_bug.cgi?id=23162. When explained
> that you would just chain x-nurk::part(woot)::part(zorp)::part(bler)
> to cross each shadow tree boundary, the guinea people looked at me
> like this: <_<. Then they pointed out that this both:
>
> a) implies ever-growing "part" API for each component, which will
> quickly lead to the anti-pattern of developers simply declaring all
> elements in their shadow tree as parts, and
>
> b) just looks ugly and constipated.
>
> Agitated shouts of "Y U NO LET US JUS DO EET" were echoing across the
> San Francisco Bay, frightening America's Cup spectators.
>
> To calm the brave guinea people down, I showed them a magic trick. Out
> of my sleeve, I pulled out two new combinators: A hat (^) and a cat
> (^^).
>
> You would use them instead of ::part. The hat is generally equivalent
> to a descendant combinator, except it crosses 1 (one) shadow tree
> boundary (from shadow host to shadow root). The cat is similar, except
> it crosses any number of boundaries. So, to target "bler" in the
> previous part-y chain could be written as simply as
> x-nurk^^[part=bler] or x-nurk^^#bler if ids are used instead of
> part="bler" attribute. Respectively, you would target "woot" as simply
> x-nurk^#woot.
>
> But wait there's more: you could use these new combinators in
> querySelector, I proclaimed! In the nascent shadow DOM code, we
> already started seeing the blood-curling
> document.querySelector('x-nurk').shadowRoot.querySelector('#woot').shadowRoot.querySelector('#zorp')
> chains of hell -- a problem that these new combinators would solve.
>
> Think of them simply general combinators that opens shadow trees for
> selector traversal, just like Element.shadowRoot did for DOM
> traversal.
>
> The brave guinea people became content and reverted to their natural
> behaviors, but I then started worrying. Did I over-promise and finally
> ruined encapsulation? When will our styling woes finally converge into
> one solution?
>
> Luckily, I have you, my glorious WebApp-erators. Put on your thinking
> hats and help find one uniform solution. Something that fits well into
> CSS, doesn't add too many new moving parts, and keeps the brave guinea
> people at bay. That'll be the day.

In-person discussion convinced me of the use of this.  Basically, we
should go ahead and expose a nice, wide feature now, rather than
trying to expose one that is locked down *just right*.  We can add
more features that are harder to misuse in the future, once we have a
better idea of the common usage patterns.

So, to be clearer, we'd be adding two new combinators, "^" and "^^".
"^", the shadow combinator (or "hat"), pierces the upper-boundary
encapsulation of a shadow root. The LHS has to be a component,
something with an actual shadow root.  "^^", the "all shadows"
combinator (or "cat", or "double-hat"), is identical, but it pierces
*all* shadow root upper boundaries.

Here's another example:

<div class="foo">
  <style>
    x-dialog ^ .bar { ... } // matches
    x-dialog ^^ .bar { ... } // matches
    x-dialog ^ .baz { ... } // doesn't match - behind a second SR boundary
    x-dialog ^ x-button ^ .baz { ... } // matches
    x-dialog ^^ .baz { ... } // matches
    .foo ^^ .baz { ... } // doesn't match - div.foo has no SR
  </style>
  <x-dialog>
    [shadow-root]
      <div class="bar">some text!</div>
      <style>
        x-button ^ .baz { ... } // matches
        x-button ^^ .baz { ... } // matches
      </style>
      <x-button>
        [shadow-root]
          <div class="baz">more text!</div>
      </x-button>
  </x-dialog>
</div>




Tangentially related to this, it came up that several of these
use-cases want to be able to target an element that is a direct child
of a shadow root, but there's no way to indicate that in selectors
currently.  Similar things have come up in other contexts, such as
running .query() on a DocumentFragment.  The CSSWG has consistently
refused to expand the meaning of :root, so we figured we could just
introduce another pseudo-class called :top or something, which matches
any element whose parent is not an element.

~TJ

Received on Thursday, 19 September 2013 23:09:52 UTC