treeselectjs 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/input.js ADDED
@@ -0,0 +1,339 @@
1
+ import svg from './svgIcons.js'
2
+
3
+ class TreeselectInput {
4
+ #htmlTagsSecton = null
5
+ #htmlEditControl = null
6
+ #htmlOperators = null
7
+ #htmlArrow = null
8
+ #openEvent = new CustomEvent('open')
9
+ #closeEvent = new CustomEvent('close')
10
+
11
+ constructor ({
12
+ value,
13
+ showTags,
14
+ clearable,
15
+ isAlwaysOpened,
16
+ searchable,
17
+ placeholder,
18
+ disabled
19
+ }) {
20
+ this.value = value
21
+
22
+ this.showTags = showTags ?? true
23
+ this.searchable = searchable ?? true
24
+ this.placeholder = placeholder ?? 'Search...'
25
+ this.clearable = clearable ?? true
26
+ this.isAlwaysOpened = isAlwaysOpened ?? false
27
+ this.disabled = disabled ?? false
28
+
29
+ this.isOpened = false
30
+ this.searchText = ''
31
+ this.srcElement = this.#createTreeselectInput()
32
+
33
+ this.#updateDOM()
34
+ }
35
+
36
+ // Public
37
+ focus () {
38
+ this.#htmlEditControl.focus()
39
+ }
40
+
41
+ blur () {
42
+ if (this.isOpened) {
43
+ this.#updateOpenClose()
44
+ }
45
+ }
46
+
47
+ updateValue (newValue) {
48
+ this.value = newValue
49
+ this.#updateTags()
50
+ this.#updateEditControl()
51
+ }
52
+
53
+ removeItem (id) {
54
+ this.value = this.value.filter(v => v.id !== id)
55
+ this.#emitInput()
56
+ this.#updateTags()
57
+ this.#updateEditControl()
58
+ }
59
+
60
+ clear () {
61
+ this.value = []
62
+ this.searchText = ''
63
+ this.#emitSearch('')
64
+ this.#emitInput()
65
+ this.#updateTags()
66
+ this.#updateEditControl()
67
+ }
68
+
69
+ openClose () {
70
+ this.#updateOpenClose()
71
+ }
72
+
73
+ #updateDOM () {
74
+ this.#updateTags()
75
+ this.#updateEditControl()
76
+ this.#updateOperators()
77
+ }
78
+
79
+ #updateTags () {
80
+ this.#htmlTagsSecton.innerHTML = ''
81
+
82
+ if (this.showTags) {
83
+ this.#htmlTagsSecton.append(...this.#createTags())
84
+ } else {
85
+ this.#htmlTagsSecton.appendChild(this.#createCountElement())
86
+ }
87
+ }
88
+
89
+ #updateOperators () {
90
+ const elements = []
91
+ this.#htmlOperators.innerHTML = ''
92
+
93
+ if (this.clearable) {
94
+ elements.push(this.#createClearButton())
95
+ }
96
+
97
+ if (!this.isAlwaysOpened) {
98
+ elements.push(this.#createInputArrow(this.isOpened))
99
+ }
100
+
101
+ if (elements.length) {
102
+ this.#htmlOperators.append(...elements)
103
+ }
104
+ }
105
+
106
+ #updateArrowDirection () {
107
+ if (!this.isAlwaysOpened) {
108
+ const arrowSvg = this.isOpened ? svg.arrowUp : svg.arrowDown
109
+ this.#htmlArrow.innerHTML = arrowSvg
110
+ }
111
+ }
112
+
113
+ #updateEditControl () {
114
+ if (this.value?.length) {
115
+ this.#htmlEditControl.removeAttribute('placeholder')
116
+ } else {
117
+ this.#htmlEditControl.setAttribute('placeholder', this.placeholder)
118
+ }
119
+
120
+ if (!this.searchable) {
121
+ this.srcElement.classList.add('treeselect-input--unsearchable')
122
+ } else {
123
+ this.srcElement.classList.remove('treeselect-input--unsearchable')
124
+ }
125
+
126
+ this.#htmlEditControl.value = this.searchText
127
+ }
128
+
129
+ #updateOpenClose () {
130
+ this.isOpened = !this.isOpened
131
+ this.#updateArrowDirection()
132
+
133
+ if (!this.isOpened) {
134
+ this.#emitClose()
135
+ } else {
136
+ this.#emitOpen()
137
+ }
138
+ }
139
+
140
+ #createTreeselectInput () {
141
+ const container = document.createElement('div')
142
+ container.classList.add('treeselect-input')
143
+ container.setAttribute('tabindex', '-1')
144
+
145
+ this.#htmlTagsSecton = this.#createTagsSection()
146
+ this.#htmlEditControl = this.#createControl()
147
+ this.#htmlOperators = this.#createOperators()
148
+
149
+ container.addEventListener('mousedown', (e) => {
150
+ e.preventDefault()
151
+
152
+ if (!this.isOpened) {
153
+ this.#updateOpenClose()
154
+ }
155
+
156
+ this.focus()
157
+ })
158
+
159
+ container.append(this.#htmlTagsSecton, this.#htmlEditControl, this.#htmlOperators)
160
+
161
+ return container
162
+ }
163
+
164
+ #createTagsSection () {
165
+ const tagsSection = document.createElement('div')
166
+ tagsSection.classList.add('treeselect-input__tags')
167
+
168
+ return tagsSection
169
+ }
170
+
171
+ #createTags () {
172
+ return this.value.map(value => {
173
+ const element = document.createElement('div')
174
+ element.classList.add('treeselect-input__tags-element')
175
+ element.setAttribute('tabindex', '-1')
176
+ element.setAttribute('tag-id', value.id)
177
+ element.setAttribute('title', value.name)
178
+
179
+ const name = this.#createTagName(value.name)
180
+ const cross = this.#createTagCross()
181
+
182
+ element.addEventListener('mousedown', (e) => {
183
+ // Two methods help to prevent mousedown on main container
184
+ e.preventDefault()
185
+ e.stopPropagation()
186
+ this.focus()
187
+ this.removeItem(value.id)
188
+ })
189
+
190
+ element.append(name, cross)
191
+
192
+ return element
193
+ })
194
+ }
195
+
196
+ #createTagName (name) {
197
+ const elementTag = document.createElement('span')
198
+ elementTag.classList.add('treeselect-input__tags-name')
199
+ elementTag.innerHTML = name
200
+
201
+ return elementTag
202
+ }
203
+
204
+ #createTagCross () {
205
+ const elementCross = document.createElement('span')
206
+ elementCross.classList.add('treeselect-input__tags-cross')
207
+ elementCross.innerHTML = svg.cross
208
+
209
+ return elementCross
210
+ }
211
+
212
+ #createCountElement () {
213
+ const countEl = document.createElement('span')
214
+ countEl.classList.add('treeselect-input__tags-count')
215
+
216
+ if (!this.value.length) {
217
+ countEl.innerHTML = ''
218
+
219
+ return countEl
220
+ }
221
+
222
+ countEl.innerHTML = this.value.length === 1
223
+ ? this.value[0].name
224
+ : `${this.value.length} elements selected`
225
+
226
+ return countEl
227
+ }
228
+
229
+ #createControl () {
230
+ const input = document.createElement('input')
231
+ input.classList.add('treeselect-input__edit')
232
+
233
+ if (this.disabled) {
234
+ input.setAttribute('tabindex', '-1')
235
+ }
236
+
237
+ input.addEventListener('keydown', (e) => {
238
+ if (e.key === 'Backspace' && !this.searchText.length && this.value.length && !this.showTags) {
239
+ this.clear()
240
+ }
241
+
242
+ if (e.key === 'Backspace' && !this.searchText.length && this.value.length) {
243
+ this.removeItem(this.value[this.value.length - 1].id)
244
+ }
245
+
246
+ if (e.code === 'Space' && (!this.searchText || !this.searchable)) {
247
+ this.#updateOpenClose()
248
+ }
249
+ })
250
+ input.addEventListener('input', (event) => {
251
+ event.stopPropagation()
252
+ const oldValue = this.searchText
253
+ const newValue = input.value.trim()
254
+
255
+ // If try to enter only spaces
256
+ if (oldValue.length === 0 && newValue.length === 0) {
257
+ input.value = ''
258
+
259
+ return
260
+ }
261
+
262
+ if (this.searchable) {
263
+ this.#emitSearch(event.target.value)
264
+
265
+ if (!this.isOpened) {
266
+ this.#updateOpenClose()
267
+ }
268
+ } else {
269
+ input.value = ''
270
+ }
271
+
272
+ this.searchText = input.value
273
+ })
274
+
275
+ return input
276
+ }
277
+
278
+ #createOperators () {
279
+ const operators = document.createElement('div')
280
+ operators.classList.add('treeselect-input__operators')
281
+
282
+ return operators
283
+ }
284
+
285
+ #createClearButton () {
286
+ const clear = document.createElement('span')
287
+ clear.classList.add('treeselect-input__clear')
288
+ clear.setAttribute('tabindex', '-1')
289
+ clear.innerHTML = svg.clear
290
+
291
+ clear.addEventListener('mousedown', (e) => {
292
+ // Two methods help to prevent mousedown on main container
293
+ e.preventDefault()
294
+ e.stopPropagation()
295
+ this.#htmlEditControl.focus()
296
+
297
+ if (this.searchText.length || this.value.length) {
298
+ this.clear()
299
+ }
300
+ })
301
+
302
+ return clear
303
+ }
304
+
305
+ #createInputArrow (isOpen) {
306
+ this.#htmlArrow = document.createElement('span')
307
+ this.#htmlArrow.classList.add('treeselect-input__arrow')
308
+ this.#htmlArrow.innerHTML = isOpen ? svg.arrowUp : svg.arrowDown
309
+
310
+ this.#htmlArrow.addEventListener('mousedown', (e) => {
311
+ // Two methods help to prevent mousedown on main container
312
+ e.stopPropagation()
313
+ e.preventDefault()
314
+ this.focus()
315
+ this.#updateOpenClose()
316
+ })
317
+
318
+ return this.#htmlArrow
319
+ }
320
+
321
+ // Emits
322
+ #emitInput () {
323
+ this.srcElement.dispatchEvent(new CustomEvent('input', { detail: this.value }))
324
+ }
325
+
326
+ #emitSearch (value) {
327
+ this.srcElement.dispatchEvent(new CustomEvent('search', { detail: value }))
328
+ }
329
+
330
+ #emitOpen () {
331
+ this.srcElement.dispatchEvent(this.#openEvent)
332
+ }
333
+
334
+ #emitClose () {
335
+ this.srcElement.dispatchEvent(this.#closeEvent)
336
+ }
337
+ }
338
+
339
+ export default TreeselectInput
package/src/list.css ADDED
@@ -0,0 +1,143 @@
1
+ .treeselect-list {
2
+ width: 100%;
3
+ box-sizing: border-box;
4
+ border: 1px solid #d7dde4;
5
+ overflow-y: auto;
6
+ background-color: #ffffff;
7
+ max-height: 300px;
8
+ }
9
+
10
+ .treeselect-list__group-container {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ .treeselect-list__item {
15
+ display: flex;
16
+ align-items: center;
17
+ box-sizing: border-box;
18
+ cursor: pointer;
19
+ height: 30px;
20
+ }
21
+
22
+ .treeselect-list__item--focused {
23
+ background-color: #f0ffff!important;
24
+ }
25
+
26
+ .treeselect-list__item--hidden {
27
+ display: none;
28
+ }
29
+
30
+ .treeselect-list__item-icon {
31
+ display: flex;
32
+ align-items: center;
33
+ cursor: pointer;
34
+ height: 20px;
35
+ width: 20px;
36
+ min-width: 20px;
37
+ }
38
+
39
+ .treeselect-list__item-icon svg {
40
+ pointer-events: none;
41
+ width: 100%;
42
+ height: 100%;
43
+ stroke: #c5c7cb;
44
+ }
45
+
46
+ .treeselect-list__item-icon:hover svg {
47
+ stroke: #838790;
48
+ }
49
+
50
+ .treeselect-list__item-checkbox-container {
51
+ width: 20px;
52
+ height: 20px;
53
+ min-width: 20px;
54
+ border: 1px solid #d7dde4;
55
+ border-radius: 3px;
56
+ position: relative;
57
+ background-color: #ffffff;
58
+ pointer-events: none;
59
+ box-sizing: border-box;
60
+ }
61
+
62
+ .treeselect-list__item-checkbox-container svg {
63
+ position: absolute;
64
+ height: 100%;
65
+ width: 100%;
66
+ }
67
+
68
+ .treeselect-list__item-checkbox {
69
+ margin: 0;
70
+ width: 0;
71
+ height: 0;
72
+ pointer-events: none;
73
+ position: absolute;
74
+ z-index: -1;
75
+ }
76
+
77
+ .treeselect-list__item-checkbox-icon {
78
+ position: absolute;
79
+ height: 100%;
80
+ width: 100%;
81
+ left: 0;
82
+ top: 0;
83
+ }
84
+
85
+ .treeselect-list__item-label {
86
+ width: 100%;
87
+ overflow: hidden;
88
+ text-overflow: ellipsis;
89
+ word-break: keep-all;
90
+ white-space: nowrap;
91
+ font-size: 14px;
92
+ padding-left: 5px;
93
+ pointer-events: none;
94
+ }
95
+
96
+ .treeselect-list__empty {
97
+ display: flex;
98
+ align-items: center;
99
+ height: 30px;
100
+ padding-left: 4px;
101
+ }
102
+
103
+ .treeselect-list__empty--hidden {
104
+ display: none;
105
+ }
106
+
107
+ .treeselect-list__empty-icon {
108
+ display: flex;
109
+ align-items: center;
110
+ }
111
+
112
+ .treeselect-list__empty-text {
113
+ font-size: 14px;
114
+ padding-left: 5px;
115
+ overflow: hidden;
116
+ text-overflow: ellipsis;
117
+ word-break: keep-all;
118
+ white-space: nowrap;
119
+ }
120
+
121
+ .treeselect-list__slot {
122
+ position: sticky;
123
+ box-sizing: border-box;
124
+ width: 100%;
125
+ max-width: 100%;
126
+ bottom: 0;
127
+ background-color: #ffffff;
128
+ }
129
+
130
+ /* checked styles */
131
+ .treeselect-list__item .treeselect-list__item-checkbox-container svg {
132
+ stroke: transparent;
133
+ }
134
+
135
+ .treeselect-list__item--checked .treeselect-list__item-checkbox-container svg,
136
+ .treeselect-list__item--partial-checked .treeselect-list__item-checkbox-container svg {
137
+ stroke: #ffffff;
138
+ }
139
+
140
+ .treeselect-list__item--checked .treeselect-list__item-checkbox-container,
141
+ .treeselect-list__item--partial-checked .treeselect-list__item-checkbox-container {
142
+ background-color: #52c67e;
143
+ }