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

Interface/class-based selector #719

Closed
jasmith79 opened this issue Dec 29, 2017 · 17 comments
Closed

Interface/class-based selector #719

jasmith79 opened this issue Dec 29, 2017 · 17 comments

Comments

@jasmith79
Copy link

Consider the following:

class MyElem extends HTMLElement {};
customElements.define('my-element', MyElem);

class MyMoreSpecificElem extends MyElem {};
customElements.define('my-more-specific-element', MyMoreSpecificElem);

In the parlance of object inheritance, instances of the second class have an 'is-a' relationship with the first: a MyMoreSpecificElem is a MyElem.

This relationship is captured in JavaScript:

let subclassInstance = document.querySelector('my-more-specific-element');
let parentClass = document.querySelector('my-element').constructor;
subclassInstance instanceof parentClass; // true

But there is no seamless way to capture this relationship using CSS selectors. All of the current CSS relational selectors (e.g. thing1 > thing2, thing1 ~ thing2) deal with document position, not inheritance.

Since the whole point of inheritance is re-use, that's kind of a bummer: if some UI library offers up a card-element and I want to make a my-fancy-card-element those styles should carry over.

Certainly one can add a CSS class or attribute (the latter being more in keeping with the best practices guidelines) in the constructor and select on that (call to super will take care of subclass instances), but that seems like a hacky workaround for a lack of something that should be built-in.

@bkardell
Copy link

I'm not sure adding a class is either a very high barrier or actually even wonky in terms of 'current state of things'. For example, there is no way to select HTMLHeadingElement and apply a style to all of the tags based on that interface.... currently. In that particular case, there's been much discussion/proposals around what to do (deliberately exposed pseudo-classes for them is one way - if that's the answer, then adding a class is a real good approximation, another is for allowing author defined aliases so additional sorts of things are possible). I'm not entirely sure what the 'right' answer is, but this feels like a question about DOM/CSS more generally than web components specific to me.

@jasmith79
Copy link
Author

jasmith79 commented Dec 29, 2017

Well, until we had webcomponents it wasn't an issue: whether or not one element was a subclass of another was implementation-dependent and not exposed to web developers. Now it is: thanks to webcomponents we can create custom tags that can inherit from each other. Even the heading element is bounded: there are only so many of them and typing out h1, h2, h3... is not that big of a deal. But allowing arbitrary subclasses opens the door to the need to select everything that is-a foo-x.

As for adding a class or attribute not being that big of a barrier, the problem is that (I thought) part of the idea of webcomponents was that we want people to create extensible elements so that others can use them, allowing a good ol cambrian explosion of front-end innovation. If every component author under the sun needs to remember to add a class in the constructor and expose that as part of the API contract so that subclasses can inherit their styles a lot of them won't. An alternative solution is to apply styles directly to the element in the constructor but that has its own set of problems.

The other option is to use shadow DOM and style :host but again, the component author has to remember/opt-in to doing so. And that could mean attaching a shadow root to an element that otherwise might not need one (at a noticeable perf cost). I believe there's an issue open on the custom elements spec repository about that right now.

@js-choi
Copy link

js-choi commented Dec 30, 2017

@bkardell A tangential but still-related question:

By “much discussion/proposals around what to do [about CSS selectors for HTMLHeadingElements]”, do you mean Tab Atkins’ CSS Extensions or something else?

I’m planning to eventually propose a :heading pseudo-class, and so I did a literature search for such prior discussions. The only ones that I’ve yet found have only been CSS Extensions (see also 2014-01-29 CSSWG Seattle F2F PM-III minutes) and a 2009 throwaway proposal by Giovanni Campagna on www-style.


The original post presumes that all major UA vendors will ever even reach consensus on implementing customized built-in elements (cf. #509, #648). Assuming that consensus does occur in favor of keeping them, then a CSS pseudo-class like :element(my-element) may be useful. :element(my-element) would in this case select instances of MyElem and MyMoreSpecificElem—i.e., any DOM Element whose class inherits from whatever class with which customElements.define registers the my-element tag. Of course, even then, there might be significant timing problems in the interactions between CSS cascading and custom-element registration…

@bkardell
Copy link

@js-choi you're probably just not looking hard enough :) I know for sure it is on the wiki and I'm nearly certain that at some point(s) in time it was even in drafts (you'd have to look through them but, for example, there were things in 'css 3' initial drafts in 1998/99 that we still don't have - this is one reason jQuery supported more selectors than browsers)... and I recall @wycats and I both pushed to close that gap. Yes, one way that was discussed is ultimately reflected in @tabatkins' draft you linked above.

I don't really think that there is significant division on custom built-ins - you can extend your own components and there is a whitelist in the spec of 'simple' non-interactive elements.

In any case, in response to both this and @jasmith79's comments, my feelings are currently still expressed in my original reply: If you want to chase this, it seems like this is the wrong place. If this is a need, I feel like the same magic should apply to native elements that extend. Seems bad if custom elements invents/has its own special magic for that to me. Just my 2 cents.

@js-choi
Copy link

js-choi commented Dec 30, 2017

@bkardell Ah, the wiki, of course. Thanks, this helps a lot!

Yes, this issue probably would belong more in w3c/csswg-drafts than here in w3c/webcomponents. And whatever approach should be as applicable to built-in Element subclasses as they would be to custom ones…Come to think of it, using custom-element tags would be problematic here because many useful Element subclasses, such as HTMLHeadingElement, have no single tag; similarly, custom element subclasses may have no registered tag, yet one may want to select all elements that in turn inherit from them. Perhaps some worklet—using some future Houdini specification and which would refer directly to the Element subclass rather than any tag—might end up being more appropriate…

@annevk
Copy link
Collaborator

annevk commented Feb 18, 2018

whatwg/html#83 contains a proposal for a :heading(level) pseudo-class. I think what's useful there is a little different from an interface-based selector though.

I'm not entirely sure how interface-based selectors would work though. I could imagine using whatwg/webidl#97, but that won't be as useful for custom elements, though I suppose we can make it work somehow. (Note that we cannot directly poke at JavaScript, it'll have to be some value(s) that cannot ever change that selectors can match against.)

@annevk
Copy link
Collaborator

annevk commented Feb 18, 2018

FWIW, I somewhat agree with @bkardell that it needs to be generic, but I think there are specific concerns here that only apply to custom elements. (To some extent they apply to normal elements too, e.g., if you do .__proto__ = null the selectors still needs to match since the value(s) has to be frozen, but with custom elements it's a little unclear what you would match against.)

@annevk annevk changed the title Add a CSS subtype selector. Interface/class-based selector Feb 18, 2018
@rniwa
Copy link
Collaborator

rniwa commented Feb 19, 2018

I guess there are two approaches to consider:

  1. Add CSS the ability to introspect the JS state.
  2. Add a mechanism to define selector matching mechanism to custom element.

(1) is probably a non-starter because we can't define when to evaluate JS states. And observing changes to JS states is extremely expensive.

@tabatkins
Copy link

Right, (1) seems pretty bad, I think we can reject that out of hand.

For the specific use-case in this thread, the subclassing isn't mutable JS state at all, right? It's defined when you call .defineElement() and can't change afterwards (or if it can, it's from some unregister() method that I'm not remembering off the top of my head). If that's the case, keying a CSS pseudo-class off of this seems totally reasonable. Maybe call it :subclass-of(<type-selector>)?

@domenic
Copy link
Collaborator

domenic commented Feb 20, 2018

Nah, subclassing can change at any time, using Object.setPrototypeOf.

@domenic
Copy link
Collaborator

domenic commented Feb 20, 2018

(At least for the case of the OP. For the case of customized built-in elements, there's an immutable relationship, but that's a bit separate.)

@rniwa
Copy link
Collaborator

rniwa commented Feb 21, 2018

Subclassing is totally mutable. In fact, you can have a Proxy class which changes its prototype each time we ask.

@jasmith79
Copy link
Author

Yeah I'm not a browser implementor but that certainly sounds expensive for a rather esoteric feature. Possibly having it tied to the inheritance hierarchy at the time .defineElement is called? That doesn't feel too restrictive to me, but I'm just one person.

@annevk
Copy link
Collaborator

annevk commented Feb 21, 2018

I think if we defined [[Brand]] in IDL for platform objects that would presumably hold a set of brands. EventTarget -> Node -> Element -> HTMLElement. (Note that we need to do this anyway, see what I referenced earlier.) We could define a selector that operates on [[Brand]].

For custom elements we could maybe store something about the hierarchy when the HTMLElement constructor is invoked. Probably not in [[Brand]] but in [[CustomBrand]] or some such. We could then define the selector to operate on both.

@caridy
Copy link

caridy commented Feb 21, 2018

Nah, subclassing can change at any time, using Object.setPrototypeOf.

certainly, but that does not apply to the inherited lifecycle callbacks, which for many folks will be unexpected. if you are using Object.setPrototypeOf to change the inheritance, you have bigger issues to worry about.

this is just a side note, not that I agree with (1) in any shape or form. on the other hand, (2) seems realistic.

@annevk
Copy link
Collaborator

annevk commented Mar 5, 2018

In Tokyo we concluded there's not enough interest to pursue this further.

@annevk annevk closed this as completed Mar 5, 2018
@domenic
Copy link
Collaborator

domenic commented Mar 5, 2018

#738 may cover many of the same use cases in a way that is reasonable to implement: you can define your base element to give itself (and any elements inheriting from it) a custom pseudo-class.

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

8 participants