Re: [csswg-drafts] [css-shadow-parts-1] Can class name selectors apply to a part? (#3431)

*Aside: this is a really nice issue thread of people providing comprehensively documented, politely argued perspectives on a complex issue.  It was very easy to catch up on the issue. Yay, you!*

I agree that state/pseudoclass selectors are necessary for fully theme-able custom elements.  The web component author should be able to define what states exist in the component that justify a different rendering, and the web page author should be able to adjust the theme accordingly.  And for a web component that contains theme-able parts, it is only natural that those parts might have states distinct from the state of the rest of the component.

While @domenic's [general rules for composability](https://github.com/w3c/csswg-drafts/issues/3431#issuecomment-453130662) are good guidance, I think @JanMiksovsky's [examples of complex widgets or data display](https://github.com/w3c/csswg-drafts/issues/3431#issuecomment-453582312) are all equally valid.

I strongly disagree with the most recent comments by Tab and Domenic, that these complex widgets are a failure of encapsulation.  Encapsulation means that the details of implementation of lower levels should be abstracted away at the top level.  It doesn't mean that the lower levels can't be complex.  It seems totally reasonable to me to have a component where the top-level light DOM defines the unique content, while the shadow DOMs generate complex widgets for modifying that content (like a rich text editor) or displaying it (like an interactive data visualization).

From a script point of you, the outer web page only cares that the inner widget is doing its job, maybe watching for updates to the light-DOM content.  But for _styling_? To create a full theme for a rich text editor, you're going to need toggle button states and focus states and selected text states and checked item in a drop-down menu states.

So that means states not only for the widget as a whole (e.g., empty, saved, invalid), but for individual repeated parts like buttons and drop-downs.

I ***do not like*** the idea of describing "a part that has a certain state" with a combination of multiple `part` keywords, as @tabatkins proposes in #3502. That is confusing pseudoelements with pseudoclasses.

However, I also do not like the idea of using regular CSS attribute selectors to snoop into the shadow DOM. We've gone down that route before, and it contradicts with the goals of encapsulation.  

So, I agree that a `:state(<ident>)` pseudoclass is the best syntax, where the state pseudoclass can either be applied on the primary selector (states of the custom element) or on a `::part(<ident>)` pseudoelement.  So these would be logically very different selectors:

```css
my-widget:state(active)::part(slider) {
/* themes for the slider when the widget is in the active state */
}
my-widget::part(slider):state(active) {
/* themes for the active slider in the widget */
}
```

## Possible `:state()` API

We need the state selector to be efficient for the browser to calculate, so we can't have it jumping down into JavaScript to check if a custom element or part currently matches a given state.

But I think we also want to support simple states like `:hover` and `:checked` on elements inside shadow DOM. Which means that it would be inefficient if the web component author needed to add and remove attributes (as suggested [above](https://github.com/w3c/csswg-drafts/issues/3431#issuecomment-446411162) and in #3502) or manually set DOM properties (as suggested in https://github.com/w3c/webcomponents/issues/738) every time these changed.

So, my recommendation: **Define states as a mapping between a state name (exposed to the outside) and a selector (tested inside the shadow DOM).**

So for example, if you have a multi-select list that is implemented as a list of checkboxes, you might have a `selected` state mapping for the `<my-option>` custom element that looks for a match to `#checkbox:checked` inside the shadow tree.  Outside the shadow tree, you don't need to know anything about that checkbox, it's `id`, or even that it is actually a checkbox, you're just testing for `my-option:state(selected)`.

For states on parts instead of on the shadow root, the selector would be tested against that part element.  And maybe also its descendants—although that could be handled by allowing the `:has()` selector, which would allow you to do things like map a `mixed` state to `:has( .item:checked ):has( .item:not(:checked) )` (i.e., the overall selector would only match if there are both checked and unchecked children).

The states applicable to the web component and each part would need to be explicitly set by the web component author.  For passing through states from nested web components, the selector could include `:state(<nested-component-state-name>)`.

I'm assuming that these state-selector pairs would be defined in a JavaScript API.  A `states` property on the custom element class, whose value is a dictionary of `{<statename>: <selector>}` pairs.
For defining states on parts, there would be a separate dictionary of dictionaries: `{<partname>: {<statename>: <selector>}}`.

As much as I like declarative APIs, trying to define this in an attribute would be a mess. The syntax proposed for the [`exportparts` attribute](https://drafts.csswg.org/css-shadow-parts/#exportparts-attr) only covers mappings of identifiers to other single-token identifiers.  It uses commas and colons as delimiters, so would require quotes inside of the attribute value to include full CSS selectors.

Attributes also can't be used to set states on the shadow root / component as a whole, and would be either redundant or inconsistent if you have many similar elements (same part type, e.g., `menu-button`) which should be exposing the same states (e.g., `pressed`, `disabled`).

Final idea:

You'll notice that most of the state names I'm suggesting match the names of existing pseudoclass selectors (`:disabled`, `:checked`, `:selected`).  That's a feature, not a bug.  This API would provide an author with the way to recreate all the native form pseudoclasses.  Or to turn it around, the native pseudoclasses would reflect states set on the closed shadow trees of native form elements by the native implementations.

The native pseudoclasses would be redefined as shorthands for the `:state()` selector.

So `:checked` would be exactly equal to `:state(checked)`.  `:disabled` would be exactly equal to `:state(disabled)`  Except with one difference—once a browser supports `:state()`, they would support any token inside there as a valid selector, whether it matches anything or not.  So it could be used to make future pseudoclasses backwards compatible (at least, the boolean ones).



-- 
GitHub Notification of comment by AmeliaBR
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/3431#issuecomment-459995872 using your GitHub account

Received on Saturday, 2 February 2019 20:15:36 UTC