MultiSelect WebComponent

by mattduffield

HTML

<script src="https://unpkg.com/htmx.org@1.6.1"></script>
<form method="POST"
  hx-post="."
  hx-vars="search_fields:getData()">
    <label>Username</label>
    <input name="username">
    <label>Search Fields:</label>
    <multi-select name="search_fields">
      <input list="fields">
      <datalist id="fields">
        <option value="name">Name</option>
        <option value="name_plural">Name Plural</option>
        <option value="label" selected="true">Label</option>
        <option value="label_plural" selected="true">Label Plural</option>
        <option value="list_url" selected="true">List URL</option>
      </datalist>
    </multi-select>
    <label>States</label>
    <select name="states" multiple>
      <option value="OR">OR</option>
      <option value="WA" selected="true">WA</option>
      <option value="NC">NC</option>
    </select>
    <button type="submit" id="get">Submit</button>
  </form>

CSS

body {
  font-family: 'Google Sans', sans-serif;
  margin: 40px
}

multi-input {
  display: inline-block;
  margin: 0 15px 15px 0;
}

button {
  background-color: #eee;
  border: 1px solid #ddd;  
  font-size: 16px;
  height: 30px;
  margin: 0 10px 20px 0;
}

body > div {
  align-items: center;
  display: flex;
  justify-content: center;
}

label {
  display: block;
  margin: 0 20px 20px 0;
}

p {
  text-align: center;
}

JavaScript

class MultiSelect extends HTMLElement {
	static formAssociated = true;
  
  constructor() {
    super();
    // Get access to the internal form control APIs
    this.internals_ = this.attachInternals();
    // internal value for this control
    this.value_ = 0;
    // This is a hack :^(.
    // ::slotted(input)::-webkit-calendar-picker-indicator doesn't work in any browser.
    // ::slotted() with ::after doesn't work in Safari.
    this.innerHTML += `
      <style>
        multi-select {
          display: flex;
          flex: 1 1 auto;
          min-height: 38px;
          border: 1px solid lightgray;
          border-radius: 5px;
          background-color: white;
        }      
        multi-select input::-webkit-calendar-picker-indicator {
          display: none;
        }
        /* NB use of pointer-events to only allow events from the × icon */
        multi-select div.item::after {
          color: black;
          content: '×';
          cursor: pointer;
          font-size: 18px;
          pointer-events: auto;
          position: absolute;
          right: 5px;
          top: -1px;
        }
      </style>`;
    this._shadowRoot = this.attachShadow({mode: 'open'});
    this._shadowRoot.innerHTML = `
      <style>
        :host {
          border: var(--multi-select-border, 1px solid #ddd);
          display: block;
          overflow: hidden;
          padding: 4px 8px;
        }
        /* NB use of pointer-events to only allow events from the × icon */
        ::slotted(div.item) {
          background-color: var(--multi-select-item-bg-color, #dedede);
          border: var(--multi-select-item-border, 1px solid #ccc);
          border-radius: 2px;
          color: #222;
          display: inline-block;
          font-size: var(--multi-select-item-font-size, 14px);
          margin: 2px;
          padding: 2px 25px 2px 5px;
          pointer-events: none;
          position: relative;
          top: -1px;
        }
        /* NB pointer-events:...