WICG / webcomponents

Web Components specifications
Other
4.38k stars 374 forks source link

Support Custom Pseudo-elements #300

Closed hayatoito closed 6 years ago

hayatoito commented 9 years ago

See the proposal from @philipwalton. https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Custom-Pseudo-Elements.md

Let me file an issue here to discuss and keep track of the proposal.

morewry commented 7 years ago

(2) It seems like ::theme() is only useful for open mode shadow DOMs that have not done appropriate forwarding for further shadow DOMs that they contain. Is that really a big enough use case? The forwarding feature seems like a cleaner way to handle this, and one that also works with closed-mode shadow DOMs.

I thought the same thing reading the initial draft.

One of the objections I heard from Polymer folk about dropping @apply for ::part() was that is made it more difficult to style all the buttons on the page, or similar. This was easy with @apply - just set --button-styles: {...}; and you were good - unless someone in the chain explicitly blocks that property, every button in the subtree will receive the styling.

I wouldn't want the outcome of dropping @apply for ::part(). My understanding is that both are candidates for standardization still and that neither supersedes the other--is that right? I think each style option: custom properties, @apply with custom property sets, and named ::parts are differently useful and each could be individually selected for a component based on what's most appropriate or combined together facilitate different levels of customization.

madeleineostoja commented 7 years ago

Echoing @morewry, wouldn't want to lose mixins. They're very powerful for doing what authors have been doing with preprocessor mixins for years (mixing in a bag of styles in many places), and don't see why they couldn't work hand in hand with parts should the need arise.

tabatkins commented 7 years ago

I thought the same thing reading the initial draft.

I anticipated this reaction, and I probably should have been more explicit about what it's doing: ::theme() is exactly the same, mechanically, as @apply. Literally, modulo some unimportant corners of the functionality, the two are doing precisely the same thing - letting you target a component arbitrarily far down in the shadow hierarchy.

It's ::part() that's new and restrictive, compared to @apply - it's also the more common case, and better for avoiding shooting yourself in the foot. Foot-shooting is much easier with ::theme(), but it's also more obvious; if you didn't realize that @apply was precisely the same amount of foot-shooty, it's just because it was better at hiding its problems from plain sight. ^_^

I wouldn't want the outcome of dropping @apply for ::part(). My understanding is that both are candidates for standardization still and that neither supersedes the other--is that right? I think each style option: custom properties, @apply with custom property sets, and named ::parts are differently useful and each could be individually selected for a component based on what's most appropriate or combined together facilitate different levels of customization.

No, not really; they're attacking the exact same problem space, and don't even have differing ergonomics to fall back on (like, say, @mixin and @extend do in Sass).

Ultimately, @apply is a bit of a dirty hack. It shifts CSS machinery into other parts of CSS, and in so doing loses a lot of functionality:

@apply happened to be a relatively easy hack to define, is all, building directly on existing machinery. (Tho there were some really annoying niggling problems with transitions and animations that we had to work out, which complicates the implementation a decent bit.) I invented it and promoted it because the earlier attempts to define ::part() were never nailed down by anyone, and there were a number of important mechanics questions that hadn't been answered (which @apply answered automatically, by virtue of piggybacking on the existing custom property machinery). Now that I think I've worked out all those issues with ::part(), tho, I'm happy to support it as a substantially better approach to solving the styling problem.

The only loss is that @apply can also be used to style elements in the same light dom, while ::part() is explicitly only for elements in a shadow. But this usage suffers from the same problems I cited above; it's much better to just expose selector hooks for the elements you want to target and use CSS like normal. (Which is precisely what ::part() is doing - giving shadows the ability to expose selector hooks for their elements when they want it.)

ghost commented 7 years ago

I find it really sad that @apply is going away. Not only I got accustomed to it (like I said), but I was also planning to use it for non-custom elements-related stuff too once @nested became a thing.

I guess I’ll have to think about another design for my stylesheets, then.

tabatkins commented 7 years ago

Echoing @morewry, wouldn't want to lose mixins. They're very powerful for doing what authors have been doing with preprocessor mixins for years (mixing in a bag of styles in many places), and don't see why they couldn't work hand in hand with parts should the need arise.

@apply really isn't very good at this, tho - it's roughly equivalent in power to a zero-arg @mixin from Sass (with the added benefit that you can set different values in different subtrees). Zero-arg mixins are convenient, but all the real useful mixins, in my experience, use arguments; you can only do pretty simple things with zero args.

@apply can't realistically be expanded into becoming an arg-full mixin syntax, either; at least not conveniently/readably. (If/when we invent "late-binding var()" you can get something approaching it, but it's not very convenient.)

Partial solutions, tho, have a tendency to suck the air out of the room for "real" solutions, unless you have an evolutionary path to the "full" solution. There isn't one for @apply to become a full mixin solution, tho; we'll just have to invent something new, and the existence of @apply already partially solving the problem will make creating a new thing much less attractive.

I'd rather just invent the full thing; getting it together will already be a huge fight. ^_^

matthewp commented 7 years ago

How would you "forward" a part, I don't quite understand that.

tabatkins commented 7 years ago

Say you're using an <x-button> component in your own shadow tree, and it exposes a "label" part that you'd like to expose as part of your own API. You can do <x-button part="label => button-label">, and now ::part(button-label) will work on you, and target the <x-button>s ::part(label).

matthewp commented 7 years ago

Can you only forward a single part, then?

tabatkins commented 7 years ago

No, the part attribute takes a comma-separated list of commands. https://tabatkins.github.io/specs/css-shadow-parts/#part-attr

madeleineostoja commented 7 years ago

Zero-arg mixins are convenient, but all the real useful mixins, in my experience, use arguments; you can only do pretty simple things with zero args

True, but I still think there's an argument to be made for theming with @apply since it works across both components/shadow roots and light DOM (vs. using classes for light DOM and ::theme for components).

Without @apply I imagine a lot of people (myself included) would fall back on mixins from their pre/post-processor of choice to achieve that kind of thing, so I guess the question is whether that should be baked into the platform or left to tooling?

othermaciej commented 7 years ago

I anticipated this reaction, and I probably should have been more explicit about what it's doing: ::theme() is exactly the same, mechanically, as @apply. Literally, modulo some unimportant corners of the functionality, the two are doing precisely the same thing - letting you target a component arbitrarily far down in the shadow hierarchy.

I may not understand @apply very well. But it seems like @apply puts control in the hands of component authors for where stuff gets applied, and the expected API contract is that the client of the component provides some custom properties with the intended styles. But @theme applies style to all parts with a given name everywhere. And the component client has to decide whether to use @part or @theme. So the component's API contract is not just a specific name of the styling hook, but also the requirement to know whether they should use ::part or ::theme with it.

Meanwhile, forwarding provides a completely component-controlled way of handling it where the client should always just say ::part.

Components authored with a closed shadow DOM will have to be done with forwarding, and in that case ::theme is useless. So different kinds of components will have different API contracts for how to style them that are dependent on an authoring choice that shouldn't be relevant to this.

In brief, it seems like telling your users to use ::theme (or leaving the choice to them) is poor authoring practice, and explicitly forwarding part names is good practice. The fact that ::theme theoretically is similar in power in some sense to @apply is not a good reason to have it. Depth of styling should be controlled by the component, not the client of the component, and leaving it to client code adds a needless confusing decision.

tabatkins commented 7 years ago

I may not understand @apply very well. But it seems like @apply puts control in the hands of component authors for where stuff gets applied, and the expected API contract is that the client of the component provides some custom properties with the intended styles. But @theme applies style to all parts with a given name everywhere. And the component client has to decide whether to use @part or @theme. So the component's API contract is not just a specific name of the styling hook, but also the requirement to know whether they should use ::part or ::theme with it.

Not really. Like I said above, ::theme() is actually the almost-direct translation of @apply's functionality back into the selector space (where this functionality belongs). Every bad implication you can imagine ::theme() having, @apply has, because inheritance lets you set the custom property arbitrarily far up in the flat tree, and it'll work it's way down to the component unless explicitly blocked (by the custom property getting set by an element somewhere between the component and the first element to set it).

::part(), on the other hand, is the better-designed API that you actually want. It, plus part-forwarding, gives you access to precisely the parts that your component chooses to give to you, and it works consistently between open and closed shadows.

If we all collectively decided that we only wanted ::part(), that's okay with me. It's the good part of the proposal anyway. I included ::theme() because it avoids throwing away any power; this power is precisely what some Polymer people talked about as useful in their early feedback to me. And if we don't allow ::theme(), we don't actually shut anything down, we just make similar usage vastly less convenient (the "big bag of custom properties" approach I talk about in the spec).

Ultimately, if you can pass a single value arbitrarily far down the flat tree without the intervening elements having to do anything (besides just fail to stop you), it seems weird to not allow sets of values; disallowing it doesn't actually add any security whatsoever, just makes it less convenient for component authors and users.

The fact that ::theme theoretically is similar in power in some sense to @apply is not a good reason to have it.

Just to reiterate, it's not similar "in some sense" - it's exactly as powerful in a direct-translation sense - @apply and ::theme() are mirrors of each other, just living in different parts of the CSS syntax space. And @apply is only very slightly more powerful var(), just way more convenient for the use-case of allowing arbitrary styling on an element (only "more power" is that it allows setting arbitrary custom properties, which can't have their names predicted ahead of time).

rniwa commented 7 years ago

Just to reiterate, it's not similar "in some sense" - it's exactly as powerful in a direct-translation sense - @apply and ::theme() are mirrors of each other, just living in different parts of the CSS syntax space. And @apply is only very slightly more powerful var(), just way more convenient for the use-case of allowing arbitrary styling on an element (only "more power" is that it allows setting arbitrary custom properties, which can't have their names predicted ahead of time).

"exactly as powerful" is a misleading statement given @apply can be used without any shadow trees even if there was an equivalent expressibility when it comes to styling parts of components across shadow boundaries. I can see some people may want to be using mix-ins outside the context of shadow trees, so for them @apply is a lot more useful than ::theme which seems to only work on parts defined inside a shadow tree.

Having said that, we're all for focusing on ::part first regardless of what happens to @apply vs ::theme since ::part is the feature what we always wanted.

matthewp commented 7 years ago

I disagree that ::theme is unimportant. Having very deep shadow trees is going to be very common. If you have to manually forward each part at every level it is going to get quite verbose. I can imagine CSS frameworks like Bootstrap having widgets that contain 3 or 4 parts. With deep trees and each level adding new parts I can see the outer components might have an absurd number of things to forward.

::theme gives you an escape hatch for the cases where you have global-esque styles, such as when you use a CSS framework. Being able to use ::theme(bootstrap-form) and define the styles only once will be a big advantage.

rniwa commented 7 years ago

I'm not discounting or denying the importance of use cases for @apply and ::theme here but I'd like to keep this thread's discussion focused on ::part. I think we need a separate thread for discussing the merits of @apply and ::theme.

jouni commented 7 years ago

One thing I’ve been wondering with the new “custom shadow parts” proposal is how do we expose the host element for theming by default? Can we add a part attribute for the host as well (by the element itself, automatically)? Or is it always necessary to forward the host element as a new shadow part?

As a simple use case, if I have a button component I want to provide themes for, which are not bundled with the component, and I’m not the author of the button component. The component has only the host element, no extra elements/parts in shadow DOM.

So, I would like to offer users an additional stylesheet they can load, which would give a new default look for the button, but also offer some extra styles/variations for it, like “small”, “large” and “primary”.

<link rel="stylesheet" href="theme-for-nice-button.html">

<!-- This would now look different than the <nice-button> component looks without the theme -->
<nice-button>Button</button>

<!-- I would like to offer these kind of additional styles as well -->
<nice-button class="small primary">Small Primary Button</nice-button>

Could the <nice-button> component automatically add a new part value for itself, so the generated DOM would look like this:

<nice-button part="nice-button">Button</button>
<nice-button part="nice-button" class="small primary">Small Primary Button</nice-button>

And I could then write the following CSS in the theme I provide:

html::theme(nice-button) {
  /* My new default styles for nice-button */
}

html::theme(nice-button).primary {
  /* Additional styles for the primary button */
}

Am I completely off? Has this use case been considered in the “custom shadow parts” proposal?

rniwa commented 7 years ago

No, that's a use case for @apply and ::theme, which should really be discussed in a separate thread.

jouni commented 7 years ago

Alright. Do we have that thread already somewhere?

tomalec commented 6 years ago

It was discussed at TPAC2017, the minutes are at https://www.w3.org/2017/11/10-webplat-minutes.html#item03

annevk commented 6 years ago

It seems this specification is still hosted in @tabatkins's private GitHub, despite there being feedback tracked here as well: https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+css-shadow-parts.

Will the CSS WG adopt this soonish?

Can we close this issue as this is now mostly a CSS WG matter?

rniwa commented 6 years ago

Again, we're strongly in favor of having this feature. We have a strong interest in implementing this feature in WebKit as well.

trusktr commented 6 years ago

Not sure if this is the right place, but as a custom elements author, I'd like a way to define toggle :hover for custom elements.

EDIT: made a new issue for it: https://github.com/w3c/webcomponents/issues/738

tabatkins commented 6 years ago

It seems this specification is still hosted in @tabatkins's private GitHub, despite there being feedback tracked here as well: https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+css-shadow-parts.

No, I just haven't marked my personal copy as being obsolete. The spec is at https://drafts.csswg.org/css-shadow-parts/ and is officially tracked by the CSSWG.

annevk commented 6 years ago

Let's close this then. I have updated https://github.com/w3c/webcomponents/blob/gh-pages/README.md to point out that proposal so we still have a centralized place to look at for where the various web component bits ended up.