Search code examples
javascripthtmlweb-componentnative-web-component

Can you 'customize' non built-in elements?


In the MDN documentation for using custom elements, they detail an example for customizing built-in elements:

customElements.define('expanding-list', ExpandingList, { extends: "ul" });

Allowing this usage:

<ul is="expanding-list">

  ...

</ul>

I am wondering if it is possible to customize another custom element in the same way? For example, if I have created an element called custom-element, and I want to have variants of it, I might want to create a new special-custom-element class, and define it in the same way, so as to be able to use it like so:

<custom-element is="special-custom-element">

  ...

</custom-element>

However, I am prompted with an error stating: Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': "custom-element" is a valid custom element name

When attempting to run the following:

customElements.define('special-custom-element', SpecialCustomElement, { extends: 'custom-element' });

Is this something I am doing wrong, or is this behaviour strictly limited to built-in elements? I'm finding it rather difficult to find any information about this behaviour other than what is on the page I linked to, so I was hoping for some advice from someone who knows the specs better.


Solution

  • You can not do it in the sense that you want since:

    There are two types of custom elements you can create:

    Autonomous custom element: Standalone elements; they don't inherit from built-in HTML elements.

    Customized built-in element: These elements inherit from — and extend — built-in HTML elements.

    customElements.define('word-count2', WordCount2, {extends: 'p'});
    

    Is for extending the built-in elements.

    You have to go the Autonomous custom element route as per the docs

    Here is an idea:

    // Create a class for the element
    class MyElement extends HTMLElement {
      constructor(text) {
        // Always call super first in constructor
        super();
        // Create a shadow root
        var shadow = this.attachShadow({
          mode: 'open'
        });
        // Create the span
        var wrapper = document.createElement('span');
        wrapper.textContent = !!text ? text : 'foo';
    
        shadow.appendChild(wrapper);
      }
    }
    
    class MyElement2 extends MyElement {
      constructor() {
        // Always call super first in constructor
        super('bar');
      }
    }
    
    // Define the new element
    customElements.define('my-element', MyElement);
    customElements.define('my-element2', MyElement2);
    <div>
      <my-element text="">
    </div>
    <div>
      <my-element2 text="">
    </div>

    Now you still could extend your class (and do customization of a build-in element) and access the output of the super so that might be useful to you and to some extend might allow you to get what you need:

    // Create a class for the element
    class WordCount extends HTMLParagraphElement {
      constructor() {
        // Always call super first in constructor
        super();
    
        // count words in element's parent element
        const wcParent = this.parentNode;
    
        function countWords(node) {
          const text = node.innerText || node.textContent;
          return text.split(/\s+/g).length;
        }
    
        const count = `Words: ${countWords(wcParent)}`;
    
        // Create a shadow root
        const shadow = this.attachShadow({
          mode: 'open'
        });
    
        // Create text node and add word count to it
        const text = document.createElement('span');
        text.textContent = count;
    
        // Append it to the shadow root
        shadow.appendChild(text);
    
        // Update count when element content changes
        text.textContent = count;
      }
    }
    
    class WordCount2 extends WordCount {
      constructor() {
        // Always call super first in constructor
        super();
        console.log(this.shadowRoot.textContent)
      }
    }
    
    // Define the new element
    customElements.define('word-count', WordCount, {
      extends: 'p'
    });
    customElements.define('word-count2', WordCount2, {
      extends: 'p'
    });
    <div>
      <h2>Sample heading</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum.
        Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc,
        pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.</p>
      <p is="word-count"></p>
    </div>
    
    <div>
      <h3>Sample heading</h3>
      <p>Lorem ipsum dolor sit amet, consec Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.</p>
      <p is="word-count2"></p>
    </div>