-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmulti-input.js
160 lines (148 loc) · 4.83 KB
/
multi-input.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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 +=
`<style>
multi-input input::-webkit-calendar-picker-indicator {
display: none;
}
/* NB use of pointer-events to only allow events from the × icon */
multi-input 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-input-border, 1px solid #ddd);
display: block;
overflow: hidden;
padding: 5px;
}
/* NB use of pointer-events to only allow events from the × icon */
::slotted(div.item) {
background-color: var(--multi-input-item-bg-color, #dedede);
border: var(--multi-input-item-border, 1px solid #ccc);
border-radius: 2px;
color: #222;
display: inline-block;
font-size: var(--multi-input-item-font-size, 14px);
margin: 5px;
padding: 2px 25px 2px 5px;
pointer-events: none;
position: relative;
top: -1px;
}
/* NB pointer-events: none above */
::slotted(div.item:hover) {
background-color: #eee;
color: black;
}
::slotted(input) {
border: none;
font-size: var(--multi-input-input-font-size, 14px);
outline: none;
padding: 10px 10px 10px 5px;
}
</style>
<slot></slot>`;
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);