Custom elements interacting

This is a small app that contains some custom elements that interact with each other. Who needs ReactJS with this? :P

by David Iglesias

HTML

<custom-app>
  <h1>Custom apps</h1>
  <p>
  This is some "light DOM" inside a <tt>custom-app</tt> Custom Element.</p>
  <p>The following list is composed of <tt>custom-view</tt> Custom Elements. They're clickable and react to their <tt>color</tt> attribute changing:
  </p>
  <ul>
    <li><custom-view color="red">Red</custom-view></li>
    <li><custom-view color="green">Green</custom-view></li>
    <li><custom-view color="blue">Blue</custom-view></li>
  </ul>
  <p>
  Color is changed by calling <tt>setColor</tt> in the <tt>custom-app</tt> Element:
  </p>
  <div class="selected-color"></div>
</custom-app>

CSS

* {
  font-family: sans-serif;
  box-sizing: border-box;
}

tt { font-family: monospace; color: green; font-size: 1.2em; background: #eee; }

.selected-color {
  width: 100px; height: 100px;
  border: 1px solid black;
}

custom-view {
  text-decoration: underline;
}

JavaScript

// This element renders a clickable span that
// calls `setColor` on a parent element of
// type `CustomApp`.
// This could extend Link,  if we wanted extra
// features like "visited", or maybe free
// cursor:pointer styling, but then you need to
// use the custom-view differently, as:
// <a is="custom-view" color="...">...</a>
// (which to me looks worse)
class CustomView extends HTMLElement {
  // react to changes in the 'color' attribute.
  static get observedAttributes() { return ['color']; }

  constructor() {
    super();
    console.log('Initializing custom-view element');
  }
  connectedCallback() {
    this.style.cursor = "pointer";
    this.style.color = this.getAttribute('color');
    this.addEventListener('click', function(e) {
      let target = e.target; // or "this"
      let color = target.getAttribute('color');
      // Find parent of type custom-app, and
      // call method there...
      if (!this._parentApp) {
        // Locating the parent app is easy
        // This would need to be re-computed
        // (or nullified) when the element is
        // disconnected.
        this._parentApp = e.target.closest('custom-app');
      }
      this._parentApp.setColor(color);
    });
  }

	attributeChangedCallback(name, oldValue, newValue) {
    // If the "color" attribute is changed from
    // the DOM...
    if (name === 'color') {
      this.style.color = newValue;
    }
  }  
}

class CustomApp extends HTMLElement {
  constructor() {
    super();
    console.log('Initializing custom-app element');    
  }
  
  connectedCallback() {
    // Grab the style here!
    let style = new CSSStyleSheet();
    style.insertRule('.selected-color { background-color: #ddd }');
    document.adoptedStyleSheets = [style];
    // for laters
    this._target = this.querySelector('.selected-color');
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`${name} changed from ${oldValue} to ${newValue}`);
  }
  
  setColor(newColor) {
    //...