Search code examples
web-componentpolymer-starter-kitlit-element

Using a Material Web Component inside a litElement


I'm trying to make a small app using the PWA Starter Kit from the Polymer project.

Is it possible to use a web component from https://material.io/develop/web/components/input-controls/text-field/ inside my LitElement? I want use a text area.

What I have tried:

import {html, customElement, LitElement} from "lit-element";
//
import {MDCTextField} from '@material/textfield';

@customElement('text-editor')
export class TextEditor extends LitElement {

    protected render() {
        return html`<div class="mdc-text-field mdc-text-field--textarea">
  <textarea id="textarea" class="mdc-text-field__input" rows="8" cols="40"></textarea>
  <div class="mdc-notched-outline">
    <div class="mdc-notched-outline__leading"></div>
    <div class="mdc-notched-outline__notch">
      <label for="textarea" class="mdc-floating-label">Textarea Label</label>
    </div>
    <div class="mdc-notched-outline__trailing"></div>
  </div>
</div>`
    }

}

However, because I don't use "MDCTextField" anywhere, the TypeScript compiler complains that "'MDCTextField' is declared but its value is never read.".

I do get a text area rendered in the HTML, but none of the styles are applied.

How can I reuse the MDCTextField web component in a LitElement?


Solution

  • Yes, you have to use LitElement's static styles which uses Constructible styles along with fallback for non-supporting browsers:

    import { html, customElement, LitElement, unsafeCSS } from 'lit-element';
    
    import { MDCTextField } from '@material/textfield';
    
    // IMPORTANT: USE WEBPACK RAW-LOADER OR EQUIVALENT
    import style from 'raw-loader!@material/textfield/dist/mdc.textfield.css';
    
    @customElement('text-editor')
    export class TextEditor extends LitElement {
    
      static styles = [unsafeCSS(style)];
    
      private textField?: MDCTextField;
    
      connectedCallback() {
    
        super.connectedCallback();
    
        const elm = this.shadowRoot!.querySelector('.mdc-text-field')! as HTMLElement;
    
        if (elm && !this.textField) {
          // Element is re-attached to the DOM
          this.makeTextField();
        }
      }
    
      disconnectedCallback() {
        if (this.textField) {
          this.textField.destroy();
          this.textField = undefined;
        }
      }
    
      render() {
        return html`
          <div class='mdc-text-field mdc-text-field--textarea'>
            <textarea id='textarea' class='mdc-text-field__input' rows='8' cols='40'></textarea>
            <div class='mdc-notched-outline'>
              <div class='mdc-notched-outline__leading'></div>
              <div class='mdc-notched-outline__notch'>
                <label for='textarea' class='mdc-floating-label'>Textarea Label</label>
              </div>
              <div class='mdc-notched-outline__trailing'></div>
            </div>
        </div>`;
      }
    
      firstUpdated() {
        // Executed just once
        this.makeTextField();
      }
    
      private makeTextField() {
        const elm = this.shadowRoot!.querySelector('.mdc-text-field')! as HTMLElement;
    
        this.textField = new MDCTextField(elm);
      }
    
    }
    

    These are the things you need to do:

    1. Use a bundler like Webpack or rollup to read CSS file as a string. In the above example, I used Sebpack with raw-loader.
    2. Initialize MDCTextField when the component is rendered for the first time using firstUpdated lifecycle event.
    3. Subsequently, a component may be removed and re-inserted into the DOM and thus you will want to destroy, cleanup and re-initialize the MDCTextField instance.