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:...