Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved parent selector support for :host ? #3126

Closed
TremayneChrist opened this issue Nov 2, 2017 · 11 comments
Closed

Improved parent selector support for :host ? #3126

TremayneChrist opened this issue Nov 2, 2017 · 11 comments

Comments

@TremayneChrist
Copy link

https://developers.google.com/web/fundamentals/web-components/shadowdom#styling

It's hard to understand how to create shared styles for native and custom elements (light vs shadow DOM)

Example:

button.less

button {
  .shared-style;
}

my-custom-button.less

:host {
  .shared-style;
}

../shared/button-styles.less

.shared-style() {
  &:hover {
    color: red;
  }
}

CSS output

button:hover {
  color: red;
}
:host:hover {
  color: red;
}

This is incorrect as :host requires any other selectors to be placed inside of parentheses.

For example:

:host(:hover) {
  color: red;
}

This can be fixed for :host by doing:

.shared-style() {
  &(:hover) {
    color: red;
  }
}

The problem is that this then breaks the button styles which is not ideal when sharing styles between native and custom elements - it all gets a bit complicated.

Breaking up the mixin into smaller parts is an option, however, as :host always requires parentheses, could {less} automatically output them in the CSS?

@seven-phases-max
Copy link
Member

seven-phases-max commented Nov 7, 2017

Well, on one hand I can guess how tempting it may seem to just "fix" the compiler to do "the right thing". On the other hand this is clearly would be the same "guess the user intentions" stuff as in #1942, #1605 etc. (Generally rejected because we'll end up with lotsa exceptional non-maintainable spagetti stacks instead of strict and straight-forward selector rendering rules. Or in other words: normally we should try to not break the "identical code should produce identical result" rule or it will break us in return).

Notice that the example is basically the same as::

:not {
  .shared-style; // same wrong result
}

etc. And for a big picture all this can be reduced to the completely artificial and ridiculous:

dy {
    &bo {color: red}
}

->

dybo {color: red} /* uhmm, shouldn't this be body instead? */

So I'd rather said this (fairly reasonable, as well as issues mentioned above) use-case needs some fundamentally different idea/design-pattern.

@TremayneChrist
Copy link
Author

Hey @seven-phases-max

I agree that the compiler should not just be the answer to fixing it.
I also agree that your examples above should compile as they are written.

You would use :not in completely different ways though (at least from what I can imagine).

// Like you said, it's the same issue as :host, however, this is very confused selector
// and is unlikely the share styles as they return very different elements.
// If they did share styles, they are likely to be minimal with simple selectors.
button, :not {
  &[attr] {
    color: red;
  }
}

// Also looks unlikely, but imagine the :host is compiled separately in another file.
// Both elements are buttons, for example, so they can share a large amount of styles.
button, :host {
  &[attr] {
    color: red;
  }
}

Currently we are wrapping :host selector after compilation, which is fine, and maybe just a plugin is the answer.

@seven-phases-max
Copy link
Member

seven-phases-max commented Nov 7, 2017

Observation #1. The following code would do the trick if extend could work within parens (or at least did not have the limitation of being the last element of a selector):

.shared-style() {
    &:hover {
        color: red;
    }
}

button {.shared-style}
host_  {.shared-style}

// adapter:

:host(:extend(host_ all)) {} 
// ^ fail: currently `extend` does not work in parens

@TremayneChrist
Copy link
Author

True, although :host is nothing until it's inside a shadowRoot and therefore having the output from an extend would not help, unfortunately. Well it could, but there would be additional selectors which aren't required.

Currently we compile custom element styles individually to separate out the multiple :host / shadow styles. These styles then get added to the shadowRoot of the component - which is why we use the mixin approach as we can just reference the mixin and apply it.

Maybe this is quite niche as we're styling native and custom elements together...

@seven-phases-max
Copy link
Member

seven-phases-max commented Nov 7, 2017

Then I'm giving up (there's no fun in trying to find a consistent pattern for such inconsistent overengineered system like this shadowWhatever).

@TremayneChrist
Copy link
Author

Haha, fair enough. Custom elements are a bit of a pain. Cheers anyway.

@seven-phases-max
Copy link
Member

seven-phases-max commented Nov 7, 2017

You're closing to early I think. I'd wait for other opinions (me is known to be against everything and whining the most). After all there may be some neat idea flying around that could make everybody happy :)...

(After all such sketching for possible workarounds sometimes makes one to step into weird never-seen-yet snippets like this for example)

@rjgotten
Copy link
Contributor

rjgotten commented Nov 11, 2017

@seven-phases-max wrote
I'd wait for other opinions

You rang? ;-)

Sounds like what @TremayneChrist is asking for here is something akin to the ! CSS Level 4 subject selector, but to mark where nested selectors would slot into their parent. Sort of like the reverse of & in Less, which determines where parent selectors slot into their nested children.

Maybe work off of a combination of both, e.g. with a !& sequence that may appear in a parent selector:

:host(!&) {
  .shared-style;
}

This type of feature could generally be quite useful, imho. I know that in the past I've run into my fair share of prickly situations where I was stuck muttering curses while refactoring large parts of complicated mixin-based components, that ended up with a neeed to insert child selectors 'into the middle' of a parent selector. And ofcourse; Less doesn't support that.

Not at all different from the :host() predicament here. (Infact; eerily similar, since more than a few were based on specific logical branching for classes that were not allowed to be applied. I.e. :not(!&) would've done the job very nicely for me back then.

@seven-phases-max
Copy link
Member

seven-phases-max commented Nov 11, 2017

slot selector

Curiously just yesterday I thought of something similar as an alternative for #1075 (which, in either of its forms, solves too narrow problem set and has too many problems fixing the others).
I did not read https://www.w3.org/TR/selectors4/#subject yet, so in my imaginations that thing turned to be a bit more complex than just a single op. Intuitively sooner or later it wil come to multiple placeholders within the same selector (e.g. :not(!&):not(!&)* {} // oops) - so the placeholder should probably be (optionally) named. Secondary (yet again it was a sketch for anti-#1075) a nested items are better to have possibly explicitly set the replacing part (not just the whole nested selector itself). And finally it should probably (also optionally) handle cases where nested items do not set any value at all (thus the parent selector should be able to set a fall back value too). With all that in mind I ended at something scary like (very raw pseudo-code),
"less":

div:hook(foo) span:not(:hook(bar, #boo)) {
    :set-hook(foo, :hover) {1:1}
    p:set-hook(foo, ::before):set-hook(bar, .btn) {2:2}
}

-> css:

div:hover span:not(#boo) {1:1}
div::before span:not(.btn) p {2:2}

(No idea how this can be simplified (all three constraints I mentioned above will make whatever syntax to be very verbose I guess), though the scariness is to be compared against either of #1075 variant limitations). In a simplest case (no name, no fallback value) the placeholder becomes just :hook then:

:host(:hook) {
   div {...} // -> :host(div)
}

(using :extend-like :hook syntax only because char-based ops tend to become ambiguous one day, e.g. if ! is a valid CSS combinator then !& thing turns to be already a valid Less code meaning something different::

div {
  !& {...} // -> !div
}

@rjgotten
Copy link
Contributor

char-based ops tend to become ambiguous one day

True. In this cases though, I think you could get away with the reverse order &! though. The Selectors level 4 subject selector is a prefix and not a suffix. (And besides; it will not be supported in the CSS profile. Only in the JS profile.)

@stale
Copy link

stale bot commented Mar 14, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants