skip to Main Content

I’d like to create a custom element to render a select input.

One parent creates <select> then a slot generates the <option> based on an array of choices.

import {html, css, LitElement} from 'lit';

export class SimpleSelect extends LitElement {
  static properties = {};

  constructor() {
    super();
  }

  render() {
    return html`<select><slot><option>None</option></slot></select>`;
  }
}
customElements.define('simple-select', SimpleSelect);

export class SimpleOptions extends LitElement {
  static properties = {
    options: {type: Array},
  };

  constructor() {
    super();
    this.options = [];
  }

  render() {
    let opts = this.options.map((el) =>
      `<option value="${el.value}">${el.label}</option>`
    );
    return html`${opts.join("")}`;
  }
}
customElements.define('simple-options', SimpleOptions);
<!DOCTYPE html>
<head>
  <script type="module" src="./simple-greeting.js"></script>
</head>
<body>
  <simple-select>
    <simple-options options='[{"label":"A","value":"a"},{"label":"B","value":"b"},{"label":"C","value":"c"}]'></simple-options>
  </simple-select>
</body>

This fails to render the slotted children; playground.

2

Answers


  1. I don’t believe this is how Lit Element is designed to be used. Looking at the lit playground code you can see that the browser doesn’t render the component as options within a select object, it renders the two custom components:

    rendered HTML

    Because LIT element uses shadowDOMs, the select component doesn’t have visibility into the option component.

    You could probably use a query selector to manually extract the options from the simple-select, however, at that point why not just combine the components? Here is an example of the combined component

    Login or Signup to reply.
  2. Why use Lit?

    As said in the comments. It is a HTML limitation; <option> must be a direct child of <select>

    <simple-select>
      v1:FOO ; v2:BAR; 
    
        v3 : BAZ
    </simple-select>
    
    <script>
      customElements.define('simple-select', class extends HTMLElement {
        connectedCallback() {
          setTimeout(() => { // wait for parsed inner DOM
            this.innerHTML = "<select>" +
              this.innerHTML
              .split(";") // split to lines
              .filter(Boolean) // ignore empty lines
              .map(line => line.trim()) // trim whitespace
              .map(line => {
                let [value, label] = line.split(":").map(x => x.trim());
                console.log(value, label);
                return `<option value="${value}">${label}</option>`
              }).join("") +
              "</select>";
          });
        }
      });
    </script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search