JSFiddle - React, Tailwind, and code Playground

by Nicholas Berlette

HTML

<div>
  Lorem ipsum biiiiatch!<br>
  <p is="word-count">
    This custom element should count the wordies!
  </p>
</div>

TypeScript

@customElement("word-count", { "extends": "p" })
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.trim().split(/\s+/g).filter(a => a.trim().length > 0).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
    setInterval(function() {
      const count = `Words: ${countWords(wcParent)}`;
      text.textContent = count;
    }, 200);
  }
}


// --------------------------- //
//        decorators.ts        //
// --------------------------- //

type Constructor<T = any, A extends any[] = any[]> = new (...args: A) => T;

type AbstractConstructor<T = any, A extends any[] = any[]> = abstract new (...args: A) => T;

/**
 * Context provided to a class decorator.
 * @template Class The type of the decorated class associated with this context.
 */
interface ClassDecoratorContext<
  Class extends Constructor = Constructor,
> {
  /** The kind of element that was decorated. */
  readonly kind: "class";
  /** The name of the decorated class. */
  readonly name: string | undefined;
  /** Adds a callback to be invoked after the definition is finalized. */
  addInitializer(initializer: (this: Class) => void): void;
}

type CustomElementInitializer = {
  <This extends Constructor = Constructor>(
    target: This,
    context: ClassDecoratorContext<This>,
  ): void;
};

interface CustomElementDecoratorOptions {
  extends?: string;
  initializer?: CustomElementInitializer;
}

type...