-
Notifications
You must be signed in to change notification settings - Fork 378
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
Custom pseudo-classes for host elements via shadow roots (:state) #738
Comments
This is an API request to manually set |
How's this different from, e.g., changing a |
Yes, or something similar. Maybe custom states, similar to your Maybe, for user land, something like
That's changing the outside state that the user should define, whereas this feature would let outside user hook into inside-defined state. I think only outside user should define classes. Not to say it isn't possible to do it that way, but it doesn't feel as clean. |
I'm pretty sure the idea of a custom state like |
Yeah, we've definitely had discussion about that in the past; I think it kinda got ignored in the larger shuffle of things surrounding Shadow DOM. ^_^ But yeah, it would be really easy to hang a set-like off of ShadowRoot (maybe DOMTokenList? I forget whether the design of that is considered a legacy mistake or not) that just listed state names that the element matches, and add |
I think this is a good idea. I'm not sure on the exact design. @tabatkins suggests putting it on shadow root, and in the past we've coupled some features there (such as custom styles, and in the future custom a11y semantics). To me putting it on custom elements makes the most sense, but I'm not sure on the design. And you could also imagine a design that works on all elements. Here's some more concrete strawpeople: Works on all elementselement.states.add("foo");
element.states.add("bar");
element.matches(":state(foo)"); // or maybe ":--foo" or similar Here Works on custom elementscustomElements.define("x-tag", class XTag extends HTMLElement {
getStatesCallback() {
const states = ["foo"];
if (this._isBar) {
states.push("bar");
}
return states;
}
}); This seems not great because it'd require calling into Works on shadow rootselement.shadowRoot.states.add("foo");
element.shadowRoot.states.add("bar");
element.matches(":state(foo)"); // or maybe ":--foo" or similar I guess in the end this ends up being pretty clean... |
Ah yeah, I guess there's no need to hook this on shadow roots; all custom elements could find this useful. The question, tho, is just how useful this is over just using classes. |
It allows your elements to expose internal states to the external world, without interfering with any user-defined classes. I.e. it allows |
That's valid. Tho if it's on all elements, it can still be fiddled with by consumers. The ShadowRoot version worked well for that; a different form of the custom element one that instead created a token list and passed it to a CE callback (for the CE to stash on its own) would give us the same ability without having to poll anything. |
This is precisely why I think this feature only makes sense on shadow root. In the case the element has some states, it's much better to just use classes. The reason you want to expose a state as opposed to modifying classes is that modifying the host element is an anti-pattern / violation of encapsulation when you have a shadow root. |
Hm, yeah, that seems like a convincing argument for why this would be tied to "has a shadow root", rather than "is a custom element" or just "is an element" - it's explicitly meant to expose something class-like, but without fiddling with the public API of the element. |
In the following example, a shadow root is not required, and the states not modifiable from outside: // if this feature is out after builtin modules, then something like
import { ElementStates } from ':system'
// otherwise
const { ElementStates } = window
import Privates from './privates-helper'
const _ = new Privates
import glUtils from './glUtils'
class GlSphere extends HTMLElement {
constructor() {
super()
_(this).states = new ElementStates( this ) // hooks into the HTML engine
}
connectedCallback() {
glUtils.whenMouseEnter(this, () => {
_(this).states.add('hover')
// ... check for CSS custom properties and update the WebGL scene ...
})
glUtils.whenMouseLeave(this, () => {
_(this).states.remove('hover')
// ... check for CSS custom properties and update the WebGL scene ...
})
}
disconnectedCallback() {
_(this).states.destroy() // so `this` can be GC'ed
}
}
customElements.define('gl-sphere', GlSphere) |
This is interesting, because, if the const el = document.createElement('gl-sphere')
const states = new ElementStates( el ) // DOMException, it was already created for that element (because gl-sphere created it in the constructor) but const el = document.createElement('div')
const states = new ElementStates( el )
states.add('foo') // it works Or, maybe the HTML engine can throw an error if |
I tend to agree with @rniwa on using shadow roots as the extension hook for all things, in order to preserve the encapsulation boundary. (I do see a small problem here with tying a11y to shadow roots in that we allow |
@annevk With respect to AOM, the idea is that using AOM property would, in effect, override the default values of builtin elements, which can then be overridden by ARIA and AOM property on the host element. |
That seems fine, but it does mean that builtin elements have a "magic" place for storing such data. Basically for builtin elements for which you can call |
I don't think so. AOM exposed on You could imagine that in the future we can add a mechanism to define the default ARIA role & values on custom elements without attaching a shadow root. Those default values should be "reflected" in default AOM values exposed on |
I think you're missing something. |
No. The default role is associated with the element class itself, not a particular instance of an element. Anyway, this discussion is way tangential to the issue of adding a mechanism to specify a state of an element so let's continue this elsewhere. |
@domenic @tabatkins I really like the idea behind |
@caridy : it would be great if you can come up with a concrete proposal for it. |
The A-Frame elements don't have shadow roots. I don't have a perf test, but seems like adding shadow roots to them all is a fair amount of weight considering how long shadow root prototype chains are, almost like duplicating the number of nodes, right? Plus shadow root cause dividing algorithm, which also adds runtime cost, which is unnecessary for Elements that don't even need it. In a case like A-Frame Elements, the goal is to save all resources for the WebGL rendering performance. |
I would suggest testing instead of speculating |
Ok, I filed #813 |
I made a proposal explainer for the API here: #832, PTAL if interested. |
@rakina Thanks for writing that proposal. It looks like it should meet our needs. One question: it's worth considering the parallelism of 1) setting of custom states and built-in pseudo-classes and 2) the application of styles to custom states and pseudo-classes. Above @tkent-google suggests using a colon in the parameter passed to
The above feels a little rough to me. A minor issue is that it feels odd to have a micro-syntax for the parameter to the I wonder if it'd be cleaner to try to use the term "state" in the API to always refer to custom state, and "pseudo-class" to always refer to built-in pseudo-classes:
This keeps custom state and built-in pseudo-classes separate. In documentation, pages like Pseudo-classes would continue to consistently talk about pseudo-classes as a built-in feature. New documentation would then talk about This makes it possible to more easily document things like the |
Is this the place for end user feedback? Or more like here? In any case, adding this here from my tweet: It would be cool to be able to save more complex data structures than just a simple present / not present. For example: string key, string valueI think there would be a lot of use cases for apis like this: /* Separate state "variable" for mode of component */
my-element::state(mode="collapsed") { ... }
my-element::state(mode="preview") { ... }
my-element::state(mode="expanded") { ... }
/* Separate state "variable" for preview source */
my-element::state(preview="item-only") { ... }
my-element::state(preview="related") { ... } other data structures?The use cases here are more questionable. More just spitballing what use cases could exist... my-parent::state(dependent=my-deeply-nested-child) { ... } |
For now, at least, you can do any (We can think about having more powerful matchers in v2, of course. But for the MVP I don't think we need them.) |
Google Chrome Canary 79.0.3939.0 or later has |
Question: does anyone foresee use cases where a
|
@dvoytenko absolutely, especially in combination with A custom element may very well want to make public a part that itself has state, and the part may not be a custom element itself. The natural way to do this would be to have the custom element set the state on the part. I would like that feature, and it's been discussed, but I would rather have basic custom state support sooner. The workaround is to make a custom element just for to have custom state settable from the outside, maybe a |
@justinfagnani that's good. I'm asking because it seems unlikely that the whole |
We made a specification-look document in WICG; https://wicg.github.io/custom-state-pseudo-class/ If this doesn't have any significant issues, I'd like to try to ship this in Google Chrome. |
@tkent-google Looks like @WebReflection has some comments over in the other thread, just in case you're not following over there: |
how can we vote for this proposal? this seems like a very semantical improvement of the salad that gets created with attributes/classes within web components |
Hi all, CSSWG discussed this feature, and raised the following issue: Switch syntax from :state(foo) to :--foo I think the change is reasonable. Please add comments to the above issue if you have feedbacks. |
I'm not a fan of the proposed change. I left a comment. WICG/custom-state-pseudo-class#6 (comment) |
I really like the idea of "attaching internal" APIs to expose internal features. I think that
would work perfectly for the scenario in the OP (which uses zero shadow roots). I have some questions and thoughts: 1) Why another new property on all elements as opposed to separate classes (or something)?One concern I have is that this adds yet one more property all elements now have. I like the separate-class idea ( #internals = new ElementInternals(this) With separate-classes, we could also split features to individual classes, making the opt-in clear: #cssSates = new ElementCSSStates(this) // Note the "CSS" to make the feature name clear, bikesheddable
#pseudoClasses = new ElementPseudoClasses(this)
//...
this.#cssStates.add("foo")
this.#pseudoClasses.add("hover") Is there a reason why all elements having an The only one thing I can really think of is that 2) Can certain performance characteristics be expressed in the spec?I hope Is optimization like this supposed to be expressed in the spec? Or do we just leave it to browsers to implement such optimizations if they desire? 3) If we have custom pseudo classes, what would happen when users try to add things like
|
I2S: https://t.co/1I7pjWEm8X Spec: https://wicg.github.io/custom-state-pseudo-class/ Bug: 1012098 Bug: WICG/webcomponents#738 Change-Id: I259d3252428e0390084650a6647dfc8c2a87a850 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2661438 Commit-Queue: Mason Freed <masonfreed@chromium.org> Auto-Submit: Kent Tamura <tkent@chromium.org> Reviewed-by: Mason Freed <masonfreed@chromium.org> Cr-Commit-Position: refs/heads/master@{#849245}
I2S: https://t.co/1I7pjWEm8X Spec: https://wicg.github.io/custom-state-pseudo-class/ Bug: 1012098 Bug: WICG/webcomponents#738 Change-Id: I259d3252428e0390084650a6647dfc8c2a87a850 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2661438 Commit-Queue: Mason Freed <masonfreed@chromium.org> Auto-Submit: Kent Tamura <tkent@chromium.org> Reviewed-by: Mason Freed <masonfreed@chromium.org> Cr-Commit-Position: refs/heads/master@{#849245} GitOrigin-RevId: 78fbd97dbf148a448be60f038701037ac6cb9151
https://developer.mozilla.org/en-US/docs/Web/CSS/:state is now Baseline, closing. 🥳 |
Some elements like the ones in A-Frame render to WebGL. They are styled with
display:none
so that DOM rendering is disabled, and the state of the custom elements are used in drawing to a canvas webgl context.It'd be great if there was a way to define when a custom element has a
:hover
/:active
/etc state so that we can do something like the following with custom elements that render in special ways:There's currently no way to make this happen (apart from parsing the CSS). Perhaps it'd be great to have an API that makes it easy to define when
:hover
state is applied to a custom element.The implementation of the element could then use a ray tracer to detect mouse hover in the WebGL context, then turn on or off the
:hover
state, allowing the user of the custom elements to easily style certain things like the radius of a sphere.The text was updated successfully, but these errors were encountered: