slim-select 1.27.1 → 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 -1970
  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 -35
  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 +317 -420
  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 -108
  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.fa8199af.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.238ee382.js +0 -2
  134. package/docs/js/app.238ee382.js.map +0 -1
  135. package/docs/js/chunk-vendors.5125e3d8.js +0 -21
  136. package/docs/js/chunk-vendors.5125e3d8.js.map +0 -1
  137. package/docs/js/home.e7b28581.js +0 -2
  138. package/docs/js/home.e7b28581.js.map +0 -1
  139. package/docs/js/install.d4300783.js +0 -2
  140. package/docs/js/install.d4300783.js.map +0 -1
  141. package/docs/js/methods.fa1608f1.js +0 -11
  142. package/docs/js/methods.fa1608f1.js.map +0 -1
  143. package/docs/js/options.2c4ceb87.js +0 -2
  144. package/docs/js/options.2c4ceb87.js.map +0 -1
  145. package/docs/js/selects.1eeba693.js +0 -2
  146. package/docs/js/selects.1eeba693.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 -775
  154. package/tslint.json +0 -26
  155. package/vue.config.js +0 -23
@@ -1,775 +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
- list.setAttribute('role', 'listbox')
557
- // @todo Link to?
558
- // list.setAttribute('aria-labelledby', '')
559
- return list
560
- }
561
-
562
- // Loop through data || filtered data and build options and append to list container
563
- public options(content: string = ''): void {
564
- const data = this.main.data.filtered || this.main.data.data
565
-
566
- // Clear out innerHtml
567
- this.list.innerHTML = ''
568
-
569
- // If content is being passed just use that text
570
- if (content !== '') {
571
- const searching = document.createElement('div')
572
- searching.classList.add(this.main.config.option)
573
- searching.classList.add(this.main.config.disabled)
574
- searching.innerHTML = content
575
- this.list.appendChild(searching)
576
- return
577
- }
578
-
579
- // If ajax and isSearching
580
- if (this.main.config.isAjax && this.main.config.isSearching) {
581
- const searching = document.createElement('div')
582
- searching.classList.add(this.main.config.option)
583
- searching.classList.add(this.main.config.disabled)
584
- searching.innerHTML = this.main.config.searchingText
585
- this.list.appendChild(searching)
586
- return
587
- }
588
-
589
- // If no results show no results text
590
- if (data.length === 0) {
591
- const noResults = document.createElement('div')
592
- noResults.classList.add(this.main.config.option)
593
- noResults.classList.add(this.main.config.disabled)
594
- noResults.innerHTML = this.main.config.searchText
595
- this.list.appendChild(noResults)
596
- return
597
- }
598
-
599
- // Append individual options to div container
600
- for (const d of data) {
601
- // Create optgroup
602
- if (d.hasOwnProperty('label')) {
603
- const item = d as Optgroup
604
- const optgroupEl = document.createElement('div')
605
- optgroupEl.classList.add(this.main.config.optgroup)
606
-
607
- // Create label
608
- const optgroupLabel = document.createElement('div')
609
- optgroupLabel.classList.add(this.main.config.optgroupLabel)
610
- if (this.main.config.selectByGroup && this.main.config.isMultiple) {
611
- optgroupLabel.classList.add(this.main.config.optgroupLabelSelectable)
612
- }
613
- optgroupLabel.innerHTML = item.label
614
- optgroupEl.appendChild(optgroupLabel)
615
-
616
- const options = item.options
617
- if (options) {
618
- for (const o of options) {
619
- optgroupEl.appendChild(this.option(o))
620
- }
621
-
622
- // Selecting all values by clicking the group label
623
- if (this.main.config.selectByGroup && this.main.config.isMultiple) {
624
- const master = this
625
- optgroupLabel.addEventListener('click', (e: MouseEvent) => {
626
- e.preventDefault()
627
- e.stopPropagation()
628
-
629
- for (const childEl of optgroupEl.children as any as HTMLDivElement[]) {
630
- if (childEl.className.indexOf(master.main.config.option) !== -1) {
631
- childEl.click()
632
- }
633
- }
634
- })
635
- }
636
- }
637
- this.list.appendChild(optgroupEl)
638
- } else {
639
- this.list.appendChild(this.option(d as Option))
640
- }
641
- }
642
- }
643
-
644
- // Create single option
645
- public option(data: Option): HTMLDivElement {
646
- // Add hidden placeholder
647
- if (data.placeholder) {
648
- const placeholder = document.createElement('div')
649
- placeholder.classList.add(this.main.config.option)
650
- placeholder.classList.add(this.main.config.hide)
651
- return placeholder
652
- }
653
-
654
- const optionEl = document.createElement('div')
655
-
656
- // Add class to div element
657
- optionEl.classList.add(this.main.config.option)
658
- // Add WCAG attribute
659
- optionEl.setAttribute('role', 'option')
660
- if (data.class) {
661
- data.class.split(' ').forEach((dataClass: string) => {
662
- optionEl.classList.add(dataClass)
663
- })
664
- }
665
-
666
- // Add style to div element
667
- if (data.style) {
668
- optionEl.style.cssText = data.style
669
- }
670
-
671
- const selected = this.main.data.getSelected() as Option
672
-
673
- optionEl.dataset.id = data.id
674
- if (this.main.config.searchHighlight && this.main.slim && data.innerHTML && this.main.slim.search.input.value.trim() !== '') {
675
- optionEl.innerHTML = highlight(data.innerHTML, this.main.slim.search.input.value, this.main.config.searchHighlighter)
676
- } else if (data.innerHTML) {
677
- optionEl.innerHTML = data.innerHTML
678
- }
679
- if (this.main.config.showOptionTooltips && optionEl.textContent) {
680
- optionEl.setAttribute('title', optionEl.textContent)
681
- }
682
- const master = this
683
- optionEl.addEventListener('click', function(e: MouseEvent) {
684
- e.preventDefault()
685
- e.stopPropagation()
686
-
687
- const element = this
688
- const elementID = element.dataset.id
689
-
690
- if (data.selected === true && master.main.config.allowDeselectOption) {
691
- let shouldUpdate = false
692
-
693
- // If no beforeOnChange is set automatically update at end
694
- if (!master.main.beforeOnChange || !master.main.config.isMultiple) {shouldUpdate = true}
695
-
696
- if (master.main.beforeOnChange && master.main.config.isMultiple) {
697
- const selectedValues = master.main.data.getSelected() as Option
698
- const currentValues = JSON.parse(JSON.stringify(selectedValues))
699
-
700
- // Remove from current selection
701
-
702
- for (let i = 0; i < currentValues.length; i++) {
703
- if (currentValues[i].id === elementID) {
704
- currentValues.splice(i, 1)
705
- }
706
- }
707
-
708
- const beforeOnchange = master.main.beforeOnChange(currentValues)
709
- if (beforeOnchange !== false) { shouldUpdate = true }
710
- }
711
-
712
- if (shouldUpdate) {
713
- if (master.main.config.isMultiple) {
714
- master.main.data.removeFromSelected((elementID as any), 'id')
715
- master.main.render()
716
- master.main.select.setValue()
717
- master.main.data.onDataChange() // Trigger on change callback
718
- } else {
719
- master.main.set('')
720
- }
721
-
722
- }
723
- } else {
724
- // Check if option is disabled or is already selected, do nothing
725
- if (data.disabled || data.selected) {
726
- return
727
- }
728
-
729
- // Check if hit limit
730
- if (master.main.config.limit && Array.isArray(selected) && master.main.config.limit <= selected.length) {
731
- return
732
- }
733
-
734
- if (master.main.beforeOnChange) {
735
- let value
736
- const objectInfo = JSON.parse(JSON.stringify(master.main.data.getObjectFromData(elementID as string)))
737
- objectInfo.selected = true
738
-
739
- if (master.main.config.isMultiple) {
740
- value = JSON.parse(JSON.stringify(selected))
741
- value.push(objectInfo)
742
- } else {
743
- value = JSON.parse(JSON.stringify(objectInfo))
744
- }
745
-
746
- const beforeOnchange = master.main.beforeOnChange(value)
747
- if (beforeOnchange !== false) {
748
- master.main.set((elementID as string), 'id', master.main.config.closeOnSelect)
749
- }
750
- } else {
751
- master.main.set((elementID as string), 'id', master.main.config.closeOnSelect)
752
- }
753
- }
754
- })
755
-
756
- const isSelected = selected && isValueInArrayOfObjects(selected, 'id', (data.id as string))
757
- if (data.disabled || isSelected) {
758
- optionEl.onclick = null
759
- if (!master.main.config.allowDeselectOption) {
760
- optionEl.classList.add(this.main.config.disabled)
761
- }
762
- if (master.main.config.hideSelectedOption) {
763
- optionEl.classList.add(this.main.config.hide)
764
- }
765
- }
766
-
767
- if (isSelected) {
768
- optionEl.classList.add(this.main.config.optionSelected)
769
- } else {
770
- optionEl.classList.remove(this.main.config.optionSelected)
771
- }
772
-
773
- return optionEl
774
- }
775
- }