class MultiInput extends HTMLElement {
constructor() {
super();
// 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 +=
``;
this._shadowRoot = this.attachShadow({mode: 'open'});
this._shadowRoot.innerHTML =
`
`;
this._datalist = this.querySelector('datalist');
this._allowedValues = [];
for (const option of this._datalist.options) {
this._allowedValues.push(option.value);
}
this._input = this.querySelector('input');
this._input.onblur = this._handleBlur.bind(this);
this._input.oninput = this._handleInput.bind(this);
this._input.onkeydown = (event) => {
this._handleKeydown(event);
};
this._allowDuplicates = this.hasAttribute('allow-duplicates');
}
// Called by _handleKeydown() when the value of the input is an allowed value.
_addItem(value) {
this._input.value = '';
const item = document.createElement('div');
item.classList.add('item');
item.textContent = value;
this.insertBefore(item, this._input);
item.onclick = () => {
this._deleteItem(item);
};
// Remove value from datalist options and from _allowedValues array.
// Value is added back if an item is deleted (see _deleteItem()).
if (!this._allowDuplicates) {
for (const option of this._datalist.options) {
if (option.value === value) {
option.remove();
};
}
this._allowedValues =
this._allowedValues.filter((item) => item !== value);
}
}
// Called when the × icon is tapped/clicked or
// by _handleKeydown() when Backspace is entered.
_deleteItem(item) {
const value = item.textContent;
item.remove();
// If duplicates aren't allowed, value is removed (in _addItem())
// as a datalist option and from the _allowedValues array.
// So — need to add it back here.
if (!this._allowDuplicates) {
const option = document.createElement('option');
option.value = value;
// Insert as first option seems reasonable...
this._datalist.insertBefore(option, this._datalist.firstChild);
this._allowedValues.push(value);
}
}
// Avoid stray text remaining in the input element that's not in a div.item.
_handleBlur() {
this._input.value = '';
}
// Called when input text changes,
// either by entering text or selecting a datalist option.
_handleInput() {
// Add a div.item, but only if the current value
// of the input is an allowed value
const value = this._input.value;
if (this._allowedValues.includes(value)) {
this._addItem(value);
}
}
// Called when text is entered or keys pressed in the input element.
_handleKeydown(event) {
const itemToDelete = event.target.previousElementSibling;
const value = this._input.value;
// On Backspace, delete the div.item to the left of the input
if (value ==='' && event.key === 'Backspace' && itemToDelete) {
this._deleteItem(itemToDelete);
// Add a div.item, but only if the current value
// of the input is an allowed value
} else if (this._allowedValues.includes(value)) {
this._addItem(value);
}
}
// Public method for getting item values as an array.
getValues() {
const values = [];
const items = this.querySelectorAll('.item');
for (const item of items) {
values.push(item.textContent);
}
return values;
}
}
window.customElements.define('multi-input', MultiInput);