slim-select 1.27.0 → 2.0.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.
Files changed (155) hide show
  1. package/.nvmrc +1 -0
  2. package/.prettierrc +6 -0
  3. package/.vscode/extensions.json +3 -0
  4. package/.vscode/settings.json +15 -5
  5. package/README.md +2 -1
  6. package/dist/helper.d.ts +1 -4
  7. package/dist/index.d.ts +38 -58
  8. package/dist/render.d.ts +109 -0
  9. package/dist/select.d.ts +31 -20
  10. package/dist/settings.d.ts +33 -0
  11. package/dist/slimselect.cjs.js +2 -0
  12. package/dist/slimselect.cjs.js.map +1 -0
  13. package/dist/slimselect.css +1 -2
  14. package/dist/slimselect.css.map +1 -0
  15. package/dist/slimselect.es.js +2 -0
  16. package/dist/slimselect.es.js.map +1 -0
  17. package/dist/slimselect.global.js +2 -0
  18. package/dist/slimselect.global.js.map +1 -0
  19. package/dist/slimselect.js +2 -1956
  20. package/dist/slimselect.js.map +1 -0
  21. package/dist/slimselect.umd.js +2 -0
  22. package/dist/slimselect.umd.js.map +1 -0
  23. package/dist/store.d.ts +71 -0
  24. package/docs/assets/check-mark.svg +6 -0
  25. package/docs/assets/data.js +59 -0
  26. package/docs/assets/home.css +1 -0
  27. package/docs/assets/home.js +1 -0
  28. package/docs/assets/index.css +1 -0
  29. package/docs/assets/index.js +16 -0
  30. package/docs/assets/index2.js +274 -0
  31. package/docs/assets/index3.js +126 -0
  32. package/docs/assets/index4.js +60 -0
  33. package/docs/assets/install.js +34 -0
  34. package/docs/assets/selects.js +55 -0
  35. package/docs/index.html +18 -5
  36. package/docs.go +3 -1
  37. package/index.html +16 -0
  38. package/jest.config.js +7 -0
  39. package/package.json +39 -39
  40. package/src/docs/app.vue +185 -76
  41. package/src/docs/assets/scss/_variables.scss +26 -10
  42. package/src/docs/assets/scss/alert.scss +12 -0
  43. package/src/docs/assets/scss/animations.scss +5 -3
  44. package/src/docs/assets/scss/button.scss +21 -0
  45. package/src/docs/assets/scss/index.scss +8 -6
  46. package/src/docs/assets/scss/input.scss +2 -2
  47. package/src/docs/assets/scss/layout.scss +195 -65
  48. package/src/docs/assets/scss/misc.scss +144 -34
  49. package/src/docs/assets/scss/prism.scss +155 -153
  50. package/src/docs/env.d.ts +12 -0
  51. package/src/docs/images/check-mark.svg +6 -0
  52. package/src/docs/images/chrome.svg +22 -0
  53. package/src/docs/images/firefox.svg +58 -0
  54. package/src/docs/images/github.png +0 -0
  55. package/src/docs/images/ie.svg +11 -0
  56. package/src/docs/images/npm.png +0 -0
  57. package/src/docs/images/opera.svg +15 -0
  58. package/src/docs/images/safari.svg +22 -0
  59. package/src/docs/main.ts +27 -0
  60. package/src/docs/pages/data.vue +130 -0
  61. package/src/docs/pages/events/addable.vue +79 -0
  62. package/src/docs/pages/events/before_after_change.vue +140 -0
  63. package/src/docs/pages/events/before_after_open_close.vue +122 -0
  64. package/src/docs/pages/events/error.vue +59 -0
  65. package/src/docs/pages/events/index.vue +33 -0
  66. package/src/docs/pages/events/search.vue +154 -0
  67. package/src/docs/pages/events/search_filter.vue +45 -0
  68. package/src/docs/pages/home.vue +96 -116
  69. package/src/docs/pages/install.vue +47 -74
  70. package/src/docs/pages/methods/destroy.vue +66 -0
  71. package/src/docs/pages/methods/enable_disable.vue +70 -0
  72. package/src/docs/pages/methods/get_data.vue +81 -0
  73. package/src/docs/pages/methods/get_selected.vue +78 -0
  74. package/src/docs/pages/methods/index.vue +36 -0
  75. package/src/docs/pages/methods/open_close.vue +63 -0
  76. package/src/docs/pages/methods/search.vue +62 -0
  77. package/src/docs/pages/methods/set_data.vue +106 -0
  78. package/src/docs/pages/methods/set_selected.vue +70 -0
  79. package/src/docs/pages/selects.vue +108 -114
  80. package/src/docs/pages/settings/always_open.vue +88 -0
  81. package/src/docs/pages/settings/close_on_select.vue +69 -0
  82. package/src/docs/pages/settings/content_location.vue +64 -0
  83. package/src/docs/pages/settings/content_position.vue +86 -0
  84. package/src/docs/pages/settings/css.vue +73 -0
  85. package/src/docs/pages/settings/data_attributes.vue +39 -0
  86. package/src/docs/pages/settings/deselect.vue +69 -0
  87. package/src/docs/pages/settings/display.vue +88 -0
  88. package/src/docs/pages/settings/hide_selected.vue +73 -0
  89. package/src/docs/pages/settings/html.vue +86 -0
  90. package/src/docs/pages/settings/index.vue +73 -0
  91. package/src/docs/pages/settings/mandatory.vue +91 -0
  92. package/src/docs/pages/settings/min_max.vue +61 -0
  93. package/src/docs/pages/settings/open_position.vue +60 -0
  94. package/src/docs/pages/settings/placeholder.vue +72 -0
  95. package/src/docs/pages/settings/search.vue +140 -0
  96. package/src/docs/pages/settings/select.vue +32 -0
  97. package/src/docs/pages/settings/select_by_group.vue +46 -0
  98. package/src/docs/pages/settings/show_tooltip.vue +44 -0
  99. package/src/docs/pages/settings/styles.vue +51 -0
  100. package/src/docs/router.ts +41 -13
  101. package/src/slim-select/helper.ts +25 -86
  102. package/src/slim-select/index.ts +318 -413
  103. package/src/slim-select/render.test.ts +39 -0
  104. package/src/slim-select/render.ts +1129 -0
  105. package/src/slim-select/rollup.config.mjs +39 -0
  106. package/src/slim-select/select.test.ts +155 -0
  107. package/src/slim-select/select.ts +280 -107
  108. package/src/slim-select/settings.ts +68 -0
  109. package/src/slim-select/slimselect.scss +263 -278
  110. package/src/slim-select/store.test.ts +220 -0
  111. package/src/slim-select/store.ts +320 -0
  112. package/src/slim-select/tsconfig.json +11 -5
  113. package/src/vue/slimselect.vue +61 -0
  114. package/tsconfig.json +10 -29
  115. package/vite.config.ts +18 -0
  116. package/.browserslistrc +0 -4
  117. package/.postcssrc.js +0 -5
  118. package/.travis.yml +0 -22
  119. package/babel.config.js +0 -5
  120. package/dist/config.d.ts +0 -83
  121. package/dist/data.d.ts +0 -50
  122. package/dist/slim.d.ts +0 -50
  123. package/dist/slimselect.min.css +0 -1
  124. package/dist/slimselect.min.js +0 -1
  125. package/dist/slimselect.min.mjs +0 -1
  126. package/docs/css/app.d5840d21.css +0 -1
  127. package/docs/css/chunk-vendors.4db6930d.css +0 -1
  128. package/docs/css/home.57020178.css +0 -1
  129. package/docs/css/install.baa5d3fb.css +0 -1
  130. package/docs/css/methods.728c742a.css +0 -1
  131. package/docs/css/options.b28a32ef.css +0 -1
  132. package/docs/css/selects.1a7e1e6a.css +0 -1
  133. package/docs/js/app.db13c724.js +0 -2
  134. package/docs/js/app.db13c724.js.map +0 -1
  135. package/docs/js/chunk-vendors.cc40aa28.js +0 -28
  136. package/docs/js/chunk-vendors.cc40aa28.js.map +0 -1
  137. package/docs/js/home.8bcb44fd.js +0 -2
  138. package/docs/js/home.8bcb44fd.js.map +0 -1
  139. package/docs/js/install.02f31028.js +0 -2
  140. package/docs/js/install.02f31028.js.map +0 -1
  141. package/docs/js/methods.1b31e988.js +0 -11
  142. package/docs/js/methods.1b31e988.js.map +0 -1
  143. package/docs/js/options.39fe1005.js +0 -2
  144. package/docs/js/options.39fe1005.js.map +0 -1
  145. package/docs/js/selects.f4d0b859.js +0 -2
  146. package/docs/js/selects.f4d0b859.js.map +0 -1
  147. package/src/docs/pages/methods.vue +0 -427
  148. package/src/docs/pages/options.vue +0 -1480
  149. package/src/main.ts +0 -29
  150. package/src/shims-vue.d.ts +0 -8
  151. package/src/slim-select/config.ts +0 -118
  152. package/src/slim-select/data.ts +0 -377
  153. package/src/slim-select/slim.ts +0 -770
  154. package/tslint.json +0 -26
  155. package/vue.config.js +0 -23
@@ -1,770 +0,0 @@
1
- import SlimSelect from './index'
2
- import { ensureElementInView, isValueInArrayOfObjects, highlight } from './helper'
3
- import { Option, Optgroup, validateOption } from './data'
4
-
5
- interface SingleSelected {
6
- container: HTMLDivElement
7
- placeholder: HTMLSpanElement
8
- deselect: HTMLSpanElement
9
- arrowIcon: {
10
- container: HTMLSpanElement
11
- arrow: HTMLSpanElement
12
- }
13
- }
14
-
15
- interface MultiSelected {
16
- container: HTMLDivElement
17
- values: HTMLDivElement
18
- add: HTMLDivElement
19
- plus: HTMLSpanElement
20
- }
21
-
22
- interface Search {
23
- container: HTMLDivElement
24
- input: HTMLInputElement
25
- addable?: HTMLDivElement
26
- }
27
-
28
- // Class is responsible for creating all the elements
29
- export class Slim {
30
- public main: SlimSelect
31
- public container: HTMLDivElement
32
- public singleSelected: SingleSelected | null
33
- public multiSelected: MultiSelected | null
34
- public content: HTMLDivElement
35
- public search: Search
36
- public list: HTMLDivElement
37
- constructor(info: { main: SlimSelect }) {
38
- this.main = info.main
39
-
40
- // Create elements in order of appending
41
- this.container = this.containerDiv()
42
- this.content = this.contentDiv()
43
- this.search = this.searchDiv()
44
- this.list = this.listDiv()
45
- this.options()
46
-
47
- this.singleSelected = null
48
- this.multiSelected = null
49
- if (this.main.config.isMultiple) {
50
- this.multiSelected = this.multiSelectedDiv()
51
- if (this.multiSelected) {
52
- this.container.appendChild(this.multiSelected.container)
53
- }
54
- } else {
55
- this.singleSelected = this.singleSelectedDiv()
56
- this.container.appendChild(this.singleSelected.container)
57
- }
58
- if (this.main.config.addToBody) {
59
- // add the id to the content as a class as well
60
- // this is important on touch devices as the close method is
61
- // triggered when clicks on the document body occur
62
- this.content.classList.add(this.main.config.id)
63
- document.body.appendChild(this.content)
64
- } else {
65
- this.container.appendChild(this.content)
66
- }
67
- this.content.appendChild(this.search.container)
68
- this.content.appendChild(this.list)
69
- }
70
-
71
- // Create main container
72
- public containerDiv(): HTMLDivElement {
73
- // Create main container
74
- const container = document.createElement('div') as HTMLDivElement
75
-
76
- // Add style and classes
77
- container.style.cssText = this.main.config.style
78
-
79
- this.updateContainerDivClass(container)
80
-
81
- return container
82
- }
83
-
84
- // Will look at the original select and pull classes from it
85
- public updateContainerDivClass(container: HTMLDivElement) {
86
- // Set config class
87
- this.main.config.class = this.main.select.element.className.split(' ')
88
-
89
- // Clear out classlist
90
- container.className = ''
91
-
92
- // Loop through config class and add
93
- container.classList.add(this.main.config.id)
94
- container.classList.add(this.main.config.main)
95
- for (const c of this.main.config.class) {
96
- if (c.trim() !== '') {
97
- container.classList.add(c)
98
- }
99
- }
100
- }
101
-
102
- public singleSelectedDiv(): SingleSelected {
103
- const container: HTMLDivElement = document.createElement('div')
104
- container.classList.add(this.main.config.singleSelected)
105
-
106
- // Placeholder text
107
- const placeholder: HTMLSpanElement = document.createElement('span')
108
- placeholder.classList.add('placeholder')
109
- container.appendChild(placeholder)
110
-
111
- // Deselect
112
- const deselect = document.createElement('span')
113
- deselect.innerHTML = this.main.config.deselectLabel
114
- deselect.classList.add('ss-deselect')
115
- deselect.onclick = (e) => {
116
- e.stopPropagation()
117
-
118
- // Dont do anything if disabled
119
- if (!this.main.config.isEnabled) {return}
120
-
121
- this.main.set('')
122
- }
123
- container.appendChild(deselect)
124
-
125
- // Arrow
126
- const arrowContainer: HTMLSpanElement = document.createElement('span')
127
- arrowContainer.classList.add(this.main.config.arrow)
128
- const arrowIcon = document.createElement('span')
129
- arrowIcon.classList.add('arrow-down')
130
- arrowContainer.appendChild(arrowIcon)
131
- container.appendChild(arrowContainer)
132
-
133
- // Add onclick for main selector div
134
- container.onclick = () => {
135
- if (!this.main.config.isEnabled) { return }
136
-
137
- this.main.data.contentOpen ? this.main.close() : this.main.open()
138
- }
139
-
140
- return {
141
- container,
142
- placeholder,
143
- deselect,
144
- arrowIcon: {
145
- container: arrowContainer,
146
- arrow: arrowIcon
147
- }
148
- }
149
- }
150
-
151
- // Based upon current selection set placeholder text
152
- public placeholder(): void {
153
- const selected = this.main.data.getSelected() as Option
154
-
155
- // Placeholder display
156
- if (selected === null || (selected && selected.placeholder)) {
157
- const placeholder = document.createElement('span')
158
- placeholder.classList.add(this.main.config.disabled)
159
- placeholder.innerHTML = this.main.config.placeholderText
160
- if (this.singleSelected) {
161
- this.singleSelected.placeholder.innerHTML = placeholder.outerHTML
162
- }
163
- } else {
164
- let selectedValue = ''
165
- if (selected) {
166
- selectedValue = selected.innerHTML && this.main.config.valuesUseText !== true ? selected.innerHTML : selected.text
167
- }
168
- if (this.singleSelected) {
169
- this.singleSelected.placeholder.innerHTML = (selected ? selectedValue : '')
170
- }
171
- }
172
- }
173
-
174
- // Based upon current selection/settings hide/show deselect
175
- public deselect(): void {
176
- if (this.singleSelected) {
177
- // if allowDeselect is false just hide it
178
- if (!this.main.config.allowDeselect) {
179
- this.singleSelected.deselect.classList.add('ss-hide')
180
- return
181
- }
182
-
183
- if (this.main.selected() === '') {
184
- this.singleSelected.deselect.classList.add('ss-hide')
185
- } else {
186
- this.singleSelected.deselect.classList.remove('ss-hide')
187
- }
188
- }
189
- }
190
-
191
- public multiSelectedDiv(): MultiSelected {
192
- const container = document.createElement('div')
193
- container.classList.add(this.main.config.multiSelected)
194
-
195
- const values = document.createElement('div')
196
- values.classList.add(this.main.config.values)
197
- container.appendChild(values)
198
-
199
- const add = document.createElement('div')
200
- add.classList.add(this.main.config.add)
201
- const plus = document.createElement('span')
202
- plus.classList.add(this.main.config.plus)
203
- plus.onclick = (e) => {
204
- if (this.main.data.contentOpen) {
205
- this.main.close()
206
- e.stopPropagation()
207
- }
208
- }
209
- add.appendChild(plus)
210
- container.appendChild(add)
211
-
212
- container.onclick = (e) => {
213
- if (!this.main.config.isEnabled) { return }
214
-
215
- // Open only if you are not clicking on x text
216
- const target = e.target as Element
217
- if (!target.classList.contains(this.main.config.valueDelete)) {
218
- this.main.data.contentOpen ? this.main.close() : this.main.open()
219
- }
220
- }
221
-
222
- return {
223
- container,
224
- values,
225
- add,
226
- plus
227
- }
228
- }
229
-
230
- // Get selected values and append to multiSelected values container
231
- // and remove those who shouldnt exist
232
- public values(): void {
233
- if (!this.multiSelected) { return }
234
- let currentNodes = this.multiSelected.values.childNodes as any as HTMLDivElement[]
235
- const selected = this.main.data.getSelected() as Option[]
236
-
237
- // Remove nodes that shouldnt be there
238
- let exists
239
- const nodesToRemove = []
240
- for (const c of currentNodes) {
241
- exists = true
242
- for (const s of selected) {
243
- if (String(s.id) === String(c.dataset.id)) {
244
- exists = false
245
- }
246
- }
247
-
248
- if (exists) { nodesToRemove.push(c) }
249
- }
250
-
251
- for (const n of nodesToRemove) {
252
- n.classList.add('ss-out')
253
- this.multiSelected.values.removeChild(n)
254
- }
255
-
256
- // Add values that dont currently exist
257
- currentNodes = this.multiSelected.values.childNodes as any as HTMLDivElement[]
258
- for (let s = 0; s < selected.length; s++) {
259
- exists = false
260
- for (const c of currentNodes) {
261
- if (String(selected[s].id) === String(c.dataset.id)) {
262
- exists = true
263
- }
264
- }
265
-
266
- if (!exists) {
267
- if (currentNodes.length === 0 || !HTMLElement.prototype.insertAdjacentElement) {
268
- this.multiSelected.values.appendChild(this.valueDiv(selected[s]))
269
- } else if (s === 0) {
270
- this.multiSelected.values.insertBefore(this.valueDiv(selected[s]), (currentNodes[s] as any))
271
- } else {
272
- (currentNodes[s - 1] as any).insertAdjacentElement('afterend', this.valueDiv(selected[s]))
273
- }
274
- }
275
- }
276
-
277
- // If there are no values set placeholder
278
- if (selected.length === 0) {
279
- const placeholder = document.createElement('span')
280
- placeholder.classList.add(this.main.config.disabled)
281
- placeholder.innerHTML = this.main.config.placeholderText
282
- this.multiSelected.values.innerHTML = placeholder.outerHTML
283
- }
284
- }
285
-
286
- public valueDiv(optionObj: Option): HTMLDivElement {
287
- const value = document.createElement('div')
288
- value.classList.add(this.main.config.value)
289
- value.dataset.id = optionObj.id
290
-
291
- const text = document.createElement('span')
292
- text.classList.add(this.main.config.valueText)
293
- text.innerHTML = (optionObj.innerHTML && this.main.config.valuesUseText !== true ? optionObj.innerHTML : optionObj.text)
294
- value.appendChild(text)
295
-
296
- if (!optionObj.mandatory) {
297
- const deleteSpan = document.createElement('span')
298
- deleteSpan.classList.add(this.main.config.valueDelete)
299
- deleteSpan.innerHTML = this.main.config.deselectLabel
300
- deleteSpan.onclick = (e) => {
301
- e.preventDefault()
302
- e.stopPropagation()
303
- let shouldUpdate = false
304
-
305
- // If no beforeOnChange is set automatically update at end
306
- if (!this.main.beforeOnChange) {shouldUpdate = true}
307
-
308
- if (this.main.beforeOnChange) {
309
- const selected = this.main.data.getSelected() as Option
310
- const currentValues = JSON.parse(JSON.stringify(selected))
311
-
312
- // Remove from current selection
313
- for (let i = 0; i < currentValues.length; i++) {
314
- if (currentValues[i].id === optionObj.id) {
315
- currentValues.splice(i, 1)
316
- }
317
- }
318
-
319
- const beforeOnchange = this.main.beforeOnChange(currentValues)
320
- if (beforeOnchange !== false) { shouldUpdate = true }
321
- }
322
-
323
- if (shouldUpdate) {
324
- this.main.data.removeFromSelected((optionObj.id as any), 'id')
325
- this.main.render()
326
- this.main.select.setValue()
327
- this.main.data.onDataChange() // Trigger on change callback
328
- }
329
- }
330
-
331
-
332
- value.appendChild(deleteSpan)
333
- }
334
-
335
- return value
336
- }
337
-
338
- // Create content container
339
- public contentDiv(): HTMLDivElement {
340
- const container = document.createElement('div')
341
- container.classList.add(this.main.config.content)
342
- return container
343
- }
344
-
345
- public searchDiv(): Search {
346
- const container = document.createElement('div')
347
- const input = document.createElement('input')
348
- const addable = document.createElement('div')
349
- container.classList.add(this.main.config.search)
350
-
351
- // Setup search return object
352
- const searchReturn: Search = {
353
- container,
354
- input
355
- }
356
-
357
- // We still want the search to be tabable but not shown
358
- if (!this.main.config.showSearch) {
359
- container.classList.add(this.main.config.hide)
360
- input.readOnly = true
361
- }
362
-
363
- input.type = 'search'
364
- input.placeholder = this.main.config.searchPlaceholder
365
- input.tabIndex = 0
366
- input.setAttribute('aria-label', this.main.config.searchPlaceholder)
367
- input.setAttribute('autocapitalize', 'off')
368
- input.setAttribute('autocomplete', 'off')
369
- input.setAttribute('autocorrect', 'off')
370
- input.onclick = (e) => {
371
- setTimeout(() => {
372
- const target = e.target as HTMLInputElement
373
- if (target.value === '') { this.main.search('') }
374
- }, 10)
375
- }
376
- input.onkeydown = (e) => {
377
- if (e.key === 'ArrowUp') {
378
- this.main.open()
379
- this.highlightUp()
380
- e.preventDefault()
381
- } else if (e.key === 'ArrowDown') {
382
- this.main.open()
383
- this.highlightDown()
384
- e.preventDefault()
385
- } else if (e.key === 'Tab') {
386
- if (!this.main.data.contentOpen) {
387
- setTimeout(() => { this.main.close() }, this.main.config.timeoutDelay)
388
- } else {
389
- this.main.close()
390
- }
391
- } else if (e.key === 'Enter') {
392
- e.preventDefault()
393
- }
394
- }
395
- input.onkeyup = (e) => {
396
- const target = e.target as HTMLInputElement
397
- if (e.key === 'Enter') {
398
- if (this.main.addable && e.ctrlKey) {
399
- addable.click()
400
- e.preventDefault()
401
- e.stopPropagation()
402
- return
403
- }
404
- const highlighted = this.list.querySelector('.' + this.main.config.highlighted) as HTMLDivElement
405
- if (highlighted) { highlighted.click() }
406
- } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
407
- // Cancel out to leave for onkeydown to handle
408
- } else if (e.key === 'Escape') {
409
- this.main.close()
410
- } else {
411
- if (this.main.config.showSearch && this.main.data.contentOpen) {
412
- this.main.search(target.value)
413
- } else {
414
- input.value = ''
415
- }
416
- }
417
- e.preventDefault()
418
- e.stopPropagation()
419
- }
420
- input.onfocus = () => { this.main.open() }
421
- container.appendChild(input)
422
-
423
- if (this.main.addable) {
424
- addable.classList.add(this.main.config.addable)
425
- addable.innerHTML = '+'
426
- addable.onclick = (e) => {
427
- if (this.main.addable) {
428
- e.preventDefault()
429
- e.stopPropagation()
430
-
431
- const inputValue = this.search.input.value
432
- if (inputValue.trim() === '') { this.search.input.focus(); return }
433
-
434
- const addableValue = this.main.addable(inputValue)
435
- let addableValueStr = ''
436
- if (!addableValue) { return }
437
-
438
- if (typeof addableValue === 'object') {
439
- const validValue = validateOption(addableValue)
440
- if (validValue) {
441
- this.main.addData(addableValue)
442
- addableValueStr = (addableValue.value ? addableValue.value : addableValue.text)
443
- }
444
- } else {
445
- this.main.addData(this.main.data.newOption({
446
- text: addableValue,
447
- value: addableValue
448
- }))
449
- addableValueStr = addableValue
450
- }
451
-
452
- this.main.search('')
453
- setTimeout(() => { // Temp fix to solve multi render issue
454
- this.main.set(addableValueStr, 'value', false, false)
455
- }, 100)
456
-
457
- // Close it only if closeOnSelect = true
458
- if (this.main.config.closeOnSelect) {
459
- setTimeout(() => { // Give it a little padding for a better looking animation
460
- this.main.close()
461
- }, 100)
462
- }
463
- }
464
- }
465
- container.appendChild(addable)
466
-
467
- searchReturn.addable = addable
468
- }
469
-
470
- return searchReturn
471
- }
472
-
473
- public highlightUp(): void {
474
- const highlighted = this.list.querySelector('.' + this.main.config.highlighted) as HTMLDivElement
475
- let prev: HTMLDivElement | null = null
476
- if (highlighted) {
477
- prev = highlighted.previousSibling as HTMLDivElement
478
- while (prev !== null) {
479
- if (prev.classList.contains(this.main.config.disabled)) {
480
- prev = prev.previousSibling as HTMLDivElement
481
- continue
482
- } else {
483
- break
484
- }
485
- }
486
- } else {
487
- const allOptions = this.list.querySelectorAll('.' + this.main.config.option + ':not(.' + this.main.config.disabled + ')')
488
- prev = allOptions[allOptions.length - 1] as HTMLDivElement
489
- }
490
-
491
- // Do not select if optgroup label
492
- if (prev && prev.classList.contains(this.main.config.optgroupLabel)) { prev = null }
493
-
494
- // Check if parent is optgroup
495
- if (prev === null) {
496
- const parent = highlighted.parentNode as HTMLDivElement
497
- if (parent.classList.contains(this.main.config.optgroup)) {
498
- if (parent.previousSibling) {
499
- const prevNodes = (parent.previousSibling as HTMLDivElement).querySelectorAll('.' + this.main.config.option + ':not(.' + this.main.config.disabled + ')')
500
- if (prevNodes.length) {
501
- prev = prevNodes[prevNodes.length - 1] as HTMLDivElement
502
- }
503
- }
504
- }
505
- }
506
-
507
- // If previous element exists highlight it
508
- if (prev) {
509
- if (highlighted) { highlighted.classList.remove(this.main.config.highlighted) }
510
- prev.classList.add(this.main.config.highlighted)
511
- ensureElementInView(this.list, prev)
512
- }
513
- }
514
-
515
- public highlightDown(): void {
516
- const highlighted = this.list.querySelector('.' + this.main.config.highlighted) as HTMLDivElement
517
- let next = null
518
-
519
- if (highlighted) {
520
- next = highlighted.nextSibling as HTMLDivElement
521
- while (next !== null) {
522
- if (next.classList.contains(this.main.config.disabled)) {
523
- next = next.nextSibling as HTMLDivElement
524
- continue
525
- } else {
526
- break
527
- }
528
- }
529
- } else {
530
- next = this.list.querySelector('.' + this.main.config.option + ':not(.' + this.main.config.disabled + ')') as HTMLDivElement
531
- }
532
-
533
- // Check if parent is optgroup
534
- if (next === null && highlighted !== null) {
535
- const parent = highlighted.parentNode as HTMLDivElement
536
- if (parent.classList.contains(this.main.config.optgroup)) {
537
- if (parent.nextSibling) {
538
- const sibling = parent.nextSibling as HTMLDivElement
539
- next = sibling.querySelector('.' + this.main.config.option + ':not(.' + this.main.config.disabled + ')') as HTMLDivElement
540
- }
541
- }
542
- }
543
-
544
- // If previous element exists highlight it
545
- if (next) {
546
- if (highlighted) { highlighted.classList.remove(this.main.config.highlighted) }
547
- next.classList.add(this.main.config.highlighted)
548
- ensureElementInView(this.list, next)
549
- }
550
- }
551
-
552
- // Create main container that options will reside
553
- public listDiv(): HTMLDivElement {
554
- const list = document.createElement('div')
555
- list.classList.add(this.main.config.list)
556
- return list
557
- }
558
-
559
- // Loop through data || filtered data and build options and append to list container
560
- public options(content: string = ''): void {
561
- const data = this.main.data.filtered || this.main.data.data
562
-
563
- // Clear out innerHtml
564
- this.list.innerHTML = ''
565
-
566
- // If content is being passed just use that text
567
- if (content !== '') {
568
- const searching = document.createElement('div')
569
- searching.classList.add(this.main.config.option)
570
- searching.classList.add(this.main.config.disabled)
571
- searching.innerHTML = content
572
- this.list.appendChild(searching)
573
- return
574
- }
575
-
576
- // If ajax and isSearching
577
- if (this.main.config.isAjax && this.main.config.isSearching) {
578
- const searching = document.createElement('div')
579
- searching.classList.add(this.main.config.option)
580
- searching.classList.add(this.main.config.disabled)
581
- searching.innerHTML = this.main.config.searchingText
582
- this.list.appendChild(searching)
583
- return
584
- }
585
-
586
- // If no results show no results text
587
- if (data.length === 0) {
588
- const noResults = document.createElement('div')
589
- noResults.classList.add(this.main.config.option)
590
- noResults.classList.add(this.main.config.disabled)
591
- noResults.innerHTML = this.main.config.searchText
592
- this.list.appendChild(noResults)
593
- return
594
- }
595
-
596
- // Append individual options to div container
597
- for (const d of data) {
598
- // Create optgroup
599
- if (d.hasOwnProperty('label')) {
600
- const item = d as Optgroup
601
- const optgroupEl = document.createElement('div')
602
- optgroupEl.classList.add(this.main.config.optgroup)
603
-
604
- // Create label
605
- const optgroupLabel = document.createElement('div')
606
- optgroupLabel.classList.add(this.main.config.optgroupLabel)
607
- if (this.main.config.selectByGroup && this.main.config.isMultiple) {
608
- optgroupLabel.classList.add(this.main.config.optgroupLabelSelectable)
609
- }
610
- optgroupLabel.innerHTML = item.label
611
- optgroupEl.appendChild(optgroupLabel)
612
-
613
- const options = item.options
614
- if (options) {
615
- for (const o of options) {
616
- optgroupEl.appendChild(this.option(o))
617
- }
618
-
619
- // Selecting all values by clicking the group label
620
- if (this.main.config.selectByGroup && this.main.config.isMultiple) {
621
- const master = this
622
- optgroupLabel.addEventListener('click', (e: MouseEvent) => {
623
- e.preventDefault()
624
- e.stopPropagation()
625
-
626
- for (const childEl of optgroupEl.children as any as HTMLDivElement[]) {
627
- if (childEl.className.indexOf(master.main.config.option) !== -1) {
628
- childEl.click()
629
- }
630
- }
631
- })
632
- }
633
- }
634
- this.list.appendChild(optgroupEl)
635
- } else {
636
- this.list.appendChild(this.option(d as Option))
637
- }
638
- }
639
- }
640
-
641
- // Create single option
642
- public option(data: Option): HTMLDivElement {
643
- // Add hidden placeholder
644
- if (data.placeholder) {
645
- const placeholder = document.createElement('div')
646
- placeholder.classList.add(this.main.config.option)
647
- placeholder.classList.add(this.main.config.hide)
648
- return placeholder
649
- }
650
-
651
- const optionEl = document.createElement('div')
652
-
653
- // Add class to div element
654
- optionEl.classList.add(this.main.config.option)
655
- if (data.class) {
656
- data.class.split(' ').forEach((dataClass: string) => {
657
- optionEl.classList.add(dataClass)
658
- })
659
- }
660
-
661
- // Add style to div element
662
- if (data.style) {
663
- optionEl.style.cssText = data.style
664
- }
665
-
666
- const selected = this.main.data.getSelected() as Option
667
-
668
- optionEl.dataset.id = data.id
669
- if (this.main.config.searchHighlight && this.main.slim && data.innerHTML && this.main.slim.search.input.value.trim() !== '') {
670
- optionEl.innerHTML = highlight(data.innerHTML, this.main.slim.search.input.value, this.main.config.searchHighlighter)
671
- } else if (data.innerHTML) {
672
- optionEl.innerHTML = data.innerHTML
673
- }
674
- if (this.main.config.showOptionTooltips && optionEl.textContent) {
675
- optionEl.setAttribute('title', optionEl.textContent)
676
- }
677
- const master = this
678
- optionEl.addEventListener('click', function(e: MouseEvent) {
679
- e.preventDefault()
680
- e.stopPropagation()
681
-
682
- const element = this
683
- const elementID = element.dataset.id
684
-
685
- if (data.selected === true && master.main.config.allowDeselectOption) {
686
- let shouldUpdate = false
687
-
688
- // If no beforeOnChange is set automatically update at end
689
- if (!master.main.beforeOnChange || !master.main.config.isMultiple) {shouldUpdate = true}
690
-
691
- if (master.main.beforeOnChange && master.main.config.isMultiple) {
692
- const selectedValues = master.main.data.getSelected() as Option
693
- const currentValues = JSON.parse(JSON.stringify(selectedValues))
694
-
695
- // Remove from current selection
696
-
697
- for (let i = 0; i < currentValues.length; i++) {
698
- if (currentValues[i].id === elementID) {
699
- currentValues.splice(i, 1)
700
- }
701
- }
702
-
703
- const beforeOnchange = master.main.beforeOnChange(currentValues)
704
- if (beforeOnchange !== false) { shouldUpdate = true }
705
- }
706
-
707
- if (shouldUpdate) {
708
- if (master.main.config.isMultiple) {
709
- master.main.data.removeFromSelected((elementID as any), 'id')
710
- master.main.render()
711
- master.main.select.setValue()
712
- master.main.data.onDataChange() // Trigger on change callback
713
- } else {
714
- master.main.set('')
715
- }
716
-
717
- }
718
- } else {
719
- // Check if option is disabled or is already selected, do nothing
720
- if (data.disabled || data.selected) {
721
- return
722
- }
723
-
724
- // Check if hit limit
725
- if (master.main.config.limit && Array.isArray(selected) && master.main.config.limit <= selected.length) {
726
- return
727
- }
728
-
729
- if (master.main.beforeOnChange) {
730
- let value
731
- const objectInfo = JSON.parse(JSON.stringify(master.main.data.getObjectFromData(elementID as string)))
732
- objectInfo.selected = true
733
-
734
- if (master.main.config.isMultiple) {
735
- value = JSON.parse(JSON.stringify(selected))
736
- value.push(objectInfo)
737
- } else {
738
- value = JSON.parse(JSON.stringify(objectInfo))
739
- }
740
-
741
- const beforeOnchange = master.main.beforeOnChange(value)
742
- if (beforeOnchange !== false) {
743
- master.main.set((elementID as string), 'id', master.main.config.closeOnSelect)
744
- }
745
- } else {
746
- master.main.set((elementID as string), 'id', master.main.config.closeOnSelect)
747
- }
748
- }
749
- })
750
-
751
- const isSelected = selected && isValueInArrayOfObjects(selected, 'id', (data.id as string))
752
- if (data.disabled || isSelected) {
753
- optionEl.onclick = null
754
- if (!master.main.config.allowDeselectOption) {
755
- optionEl.classList.add(this.main.config.disabled)
756
- }
757
- if (master.main.config.hideSelectedOption) {
758
- optionEl.classList.add(this.main.config.hide)
759
- }
760
- }
761
-
762
- if (isSelected) {
763
- optionEl.classList.add(this.main.config.optionSelected)
764
- } else {
765
- optionEl.classList.remove(this.main.config.optionSelected)
766
- }
767
-
768
- return optionEl
769
- }
770
- }