Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to select sub-classed Custom Elements with CSS

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 I can't think of any way to select all MyElems (including the subclass MyMoreSpecificElem) using CSS selectors. A tag selector would only get the superclass or subclass, and all of the relationship description selectors I'm aware of (e.g. ~, >) are about position in the document, not class hierarchy.

I mean, sure, I can add a CSS class in the constructor and select by that, the call to super would ensure that even subclass instances could be selected that way. But that's gnarly. Is there a way to do this in pure CSS?

like image 682
Jared Smith Avatar asked Oct 29 '22 21:10

Jared Smith


1 Answers

Why not have the base component class add either a className value or an attribute that will be set for both the base component class and all of the sub-component classes. Then your CSS can be set based on this className value or attribute.

class MyBaseEl extends HTMLElement {
  connectedCallback() {
    this.classList.add('my-base-el');
    this.innerHTML = '<div>Base El</div>';
  }
}

customElements.define('my-base-el', MyBaseEl);

class MySubEl extends MyBaseEl {
  connectedCallback() {
    super.connectedCallback();
    this.innerHTML = '<div>Sub El</div>';
  }
}

customElements.define('my-sub-el', MySubEl);
.my-base-el {
  background-color: #FF0;
  display: block;
  outline: 1px dashed black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.22/webcomponents-lite.js"></script>
<my-base-el></my-base-el>
<my-sub-el></my-sub-el>

This uses a className value to allow the CSS to get at all of the elements that are MyBaseEl or a subclass.

Or like this:

class MyBaseEl extends HTMLElement {
  connectedCallback() {
    this.setAttribute('my-base-el', '');
    this.innerHTML = '<div>Base El</div>';
  }
}

customElements.define('my-base-el', MyBaseEl);

class MySubEl extends MyBaseEl {
  connectedCallback() {
    super.connectedCallback();
    this.innerHTML = '<div>Sub El</div>';
  }
}

customElements.define('my-sub-el', MySubEl);
[my-base-el] {
  background-color: #FF0;
  display: block;
  outline: 1px dashed black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.22/webcomponents-lite.js"></script>
<my-base-el></my-base-el>
<my-sub-el></my-sub-el>

This uses an attribute to allow the CSS to get at all of the elements that are MyBaseEl or a subclass.

UPDATE

The only other way of setting CSS for multiple element tags is to know what all of them are and make the css work for all of them like this:

my-base-el, my-sub-el, my-other-sub-el, the-third-sub-el {
  background: #000;
  color: #fff;
}

But, if someone else can create additional sub-classes then you would need to add to the list.

UPDATE 2

Why not just set the :host in the base class and have it use a CSS variable for the styles you want the user to be able to change. Then everything will inherit that :host css and display like you want.

like image 112
Intervalia Avatar answered Nov 14 '22 14:11

Intervalia