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
[css-shadow-parts] consider moving part-mapping to style rules #2904
Comments
My initial (and not particularly carefully considered) reaction is that it feels rather complex; it's a good bit of additional syntax both for implementors to implement and for developers to learn. If the value it provides is worth the complexity, then it seems worth considering, but it's not clear to me whether it is. |
This seems like proposing to make selector matching depend on cascading result... It's unclear to me whether this setup is going to work. |
@upsuper If you think of ::part(foo) { @outer-name() } as a style rule that sets a property on all matching elements as part of the cascade then yes. But there is no property being set. So you could just think of it as a way to specify the part mappings (that is clearer in some of the alternative versions where it's an I can imagine that this idea could be quite objectionable itself. So maybe an
|
@dbaron I think you actually have less custom syntax in some versions of this because it's just selectors and a pseudo-property "outer-name". You've gotten rid of the comma-separated partmap attribute entirely. However you do have some new and maybe unexpected semantics to deal with. |
Hey, we are developing a pre-processor called Stylable, which extends the idea of scoping stylesheets with the ability to expose a "style API" for inner parts & states. Improving developer experience with a static language server that provides completions and warnings for this kind of style API. This topic is interesting to me, because although Stylable currently implements scoping by namespacing the output CSS in build time, it could potentially provide the same developer experience for styling Web-Components. and hopefully output CSS that is more similar to the source CSS, moving the scoping to the browser engine. So currently Stylable treats any CSS class as a potential custom pseudo-element (similar to /* btn.st.css */
.label { color:red; } /* panel.st.css */
:import {
-st-from: './btn.st.css';
-st-default: Btn; /* import Btn definition */
}
.okBtn {
-st-extends: Btn; /* give okBtn a Btn interface */
}
.okBtn::label { color:green; } /* access the label */
Which will be transpiled into: .btn__label { color:red; }
.panel__okBtn .btn__label { color:green; } If css shadow parts will allow defining parts through CSS definition, it would help tools such as Stylable to provide tooling to enhance web components authoring. For example we could output the previous CSS for web components (with the /* btn.st.css */
.label {
@outer-name(label);
color:red;
} /* panel.st.css */
.okBtn::part(label) { color:green; } |
thanks @fergald for creating the issue. There are few more things from the meeting yesterday that I want to give more context on why we are proposing this:
Yes, there are some cons:
|
@caridy I think your example in 'equivalence" is not correct, style= shouldn't be a selector-declaration pair, it should just be the inside of the declaration. So, it would have to be |
If there is ever any future pure-HTML use case for and API like part, for exposing bits of shadow trees in a controlled way, then we should leave it as a purely HTML feature and not bring style sheets into it it. |
@rniwa @domenic @tabatkins please take a look at this idea. I'm not a huge fan of it (having already implemented the existing spec). It seems a bit weird and is quite different to the existing proposal but it does seem to have some upsides and I don't see an immediate reason to dismiss it. It would be great to hear strong reasons one way or the other. |
This seems like a pretty powerful alternative to the current spec. It seems like it could be the exclusive way to describe parts. Consider:
This would make it pretty easy to define parts that match complex selectors. For example, this would make it easier to expose parts for the "red" and "black" squares on a checkerboard. If this is at all feasible to implement it seems worth exploring, at least briefly. |
I don't think we want to implement something this complicated. It would mean we'd need to go find these part-defining rules separately and run them at a completely different timing than when we actually apply styles. This would also make it very expensive to implement any IDL attribute for the mapping, which seems highly undesirable. |
I'm not sure why this makes IDL expensive, the map is in the stylesheet,
so you mutate it as you would any other rule in the stylesheet. There
wouldn't be a partmap attribute or equivalent IDL.
The implementation is the biggest concern for me. At least in blink it
seems fairly implementable since Blink goes element by element. For each
element it moves up the tree, so as we enter a new scope we would run these
rules first to find out what the current element is named in this scope. It
doesn't seem very hard but it definitely is special, that said you have
have something doing roughly the same thing to apply the partmap as you
move up through scopes. Whether you base it on an explicit map or on
something like this.
|
Manipulating rules is decidedly worse than exposing This proposal is definitely a lot harder to implement in WebKit than the DOM-based alternative, and it would be quite a bit slower to run. |
The ergonomics are debatable but I do think the selector based version is more powerful. However, I also think the attribute based version (which is obviously much father along) makes sense and is workable. |
I'm unsure of implementation costs, but after thinking about it a bit, defining parts in CSS is definitely more powerful, especially with pseudo-classes and elements because you can abstract away the pseudo-class from the outside. Some examples I thought of: Defining parts of a checkerboard: .cell:nth-child(odd) {
@outer-name(odd-cell);
}
.cell:nth-child(even) {
@outer-name(even-cell);
} Disabling arbitrary elements and inputs: :disabled, [disabled] {
@outer-name(disabled);
} Adding part names for light children and shadow children via ::slotted(): ::slotted(.item), .item {
@outer-name(item);
} (Think of this when combined with display: contents on the host to treat light and shadow children as siblings for flex and grid layouts. It be nice to give a single part name rather than have the outside select the parts and children separately.) Exposing a pseudo-element like ::before li::before {
@outer-name(marker);
content: counter(li);
} I'm sure there's some other cases where a component wants hover, active, etc states abstracted or styled the same as some other parts. |
The problem is that the power comes with a cost. Being able to change the presence of a part based on :hover, etc... would require a new mechanism to keep track of those states separately from actual style changes. Style invalidation would be a nightmare because now we'd need to first resolve part selectors to see if any part names have changed, then schedule a secondary path to actually invalidate elements, etc... Honestly, if we picked this syntax, we'd probably never get around to implement this feature. |
What about simplifying it to work only for classes or ids? |
If we did that, what's the point of doing this in CSS? Also, we'd still have to keep matching those IDs & classes to find out if any given element defines a part or not. Here's another problem. If we did this in CSS, I bet my money on someone coming around and asking for a DOM event whenever the presence of a part changes (i.e. appears / disappears). We won't be able to implement that because it would basically involve browser engines constantly evaluating CSS selectors which define parts to decide whether any given DOM mutation should result in the firing of such an event. |
I wrote my case above. Not having the ability to define the parts in CSS, means that my users will have to maintain a more complicated source, or compromise on custom run-time code in order to support web components.
I agree, this is another level of abstraction between CSS an DOM.
I don't know the implementation internals that you are, obviously, aware of, but I think that there are lots of use cases where allowing the developer to hook into changes in the CSS with JavaScript will improve the way we build web applications today. also:
That being said, I accept that you have more knowledge of the implementation risks. And if this is an implementation / performance issue, then it's beyond my current understanding. However, API-wise, I still think having the parts definitions as part of the style, which is also the context in which a developer targets these parts, is better. |
That can be easily done if this were DOM API because then detecting any change to whether a given element is exposed as a part or not is very fast & easy. Detecting whether a arbitrary set of selectors would match or cease to match an element is an extremely expensive operation. This is why modern browser engines match selectors lazily when updating the styles.
|
We're generally fine with dropping this idea. Vast majority of part mappings will be static and thus easily inlineable in the first place; the tiny fraction that aren't can be done with mutation observers or some other state-tracking. On the other hand, Ryosuke's speed concerns are definitely legitimate, plus weirdness like making slot-ness depend on a So, closing. |
This came up in a meeting with Salesforce. It seems reasonable to consider this option.
The current spec provides the partmap attribute to allow a part inside a sub-component to be stylable from outside the containing component. This means that forwarding rules are spread across many elements, e.g.
An possible alternative to that would be to include the part-mapping information in the style rules. There are several ways we could do this e.g.
Alternative ways of expressing this could be e.g.
Good points:
::part(theme-name) { @outer-name()}
to expose all the theme-name parts.some-class { @outer-name(some-part) }
would be the same as addingpart="some-part"
to all of the elements of class "some-class"Possible objections:
This is a fairly radical departure from the current spec and if people think this is a great/terrible idea, I'd like to hear that quickly. I think there's no difference in what can be expressed in either case, the main points are:
[ Edited by @dbaron to change a file: URL to the https: URL for the spec. ]
The text was updated successfully, but these errors were encountered: