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
@@ -0,0 +1,39 @@
1
+ import typescript from '@rollup/plugin-typescript'
2
+ import { terser } from 'rollup-plugin-terser'
3
+
4
+ export default {
5
+ input: './index.ts',
6
+ output: [
7
+ {
8
+ name: 'SlimSelect',
9
+ file: '../../dist/slimselect.js',
10
+ format: 'es',
11
+ sourcemap: true,
12
+ },
13
+ {
14
+ name: 'SlimSelect',
15
+ file: '../../dist/slimselect.es.js',
16
+ format: 'es',
17
+ sourcemap: true,
18
+ },
19
+ {
20
+ name: 'SlimSelect',
21
+ file: '../../dist/slimselect.cjs.js',
22
+ format: 'cjs',
23
+ sourcemap: true,
24
+ },
25
+ {
26
+ name: 'SlimSelect',
27
+ file: '../../dist/slimselect.global.js',
28
+ format: 'iife',
29
+ sourcemap: true,
30
+ },
31
+ {
32
+ name: 'SlimSelect',
33
+ file: '../../dist/slimselect.umd.js',
34
+ format: 'umd',
35
+ sourcemap: true,
36
+ },
37
+ ],
38
+ plugins: [typescript(), terser()],
39
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ 'use strict'
6
+
7
+ import { describe, expect, test } from '@jest/globals'
8
+ import Select from './select'
9
+ import Store, { Optgroup, Option } from './store'
10
+
11
+ describe('select module', () => {
12
+ test('constructor', () => {
13
+ document.body.innerHTML = '<select id="test"></select>'
14
+
15
+ let selectElement = document.getElementById('test') as HTMLSelectElement
16
+ let select = new Select(selectElement)
17
+ expect(select).toBeInstanceOf(Select)
18
+ })
19
+
20
+ test('get data from select options', () => {
21
+ document.body.innerHTML = `<select id="test">
22
+ <option value="1">One</option>
23
+ <option value="2">Two</option>
24
+ </select>`
25
+
26
+ let selectElement = document.getElementById('test') as HTMLSelectElement
27
+ let select = new Select(selectElement)
28
+ let data = select.getData() as Option[]
29
+ expect(data.length).toBe(2)
30
+ expect(data[0].value).toBe('1')
31
+ expect(data[0].text).toBe('One')
32
+ expect(data[1].value).toBe('2')
33
+ expect(data[1].text).toBe('Two')
34
+ })
35
+
36
+ test('get data from select optgroups', () => {
37
+ document.body.innerHTML = `<select id="test">
38
+ <optgroup label="test1">
39
+ <option value="1">One</option>
40
+ <option value="2">Two</option>
41
+ </optgroup>
42
+ <optgroup label="test2">
43
+ <option value="3">Three</option>
44
+ <option value="4">Four</option>
45
+ <option value="5">Five</option>
46
+ </optgroup>
47
+ </select>`
48
+
49
+ let selectElement = document.getElementById('test') as HTMLSelectElement
50
+ let select = new Select(selectElement)
51
+ let data = select.getData() as Optgroup[]
52
+ expect(data.length).toBe(2)
53
+ expect(data[0].label).toBe('test1')
54
+ expect(data[0].options.length).toBe(2)
55
+ expect(data[0].options[0].value).toBe('1')
56
+ expect(data[0].options[0].text).toBe('One')
57
+ expect(data[0].options[1].value).toBe('2')
58
+ expect(data[0].options[1].text).toBe('Two')
59
+ expect(data[1].label).toBe('test2')
60
+ expect(data[1].options.length).toBe(3)
61
+ expect(data[1].options[0].value).toBe('3')
62
+ expect(data[1].options[0].text).toBe('Three')
63
+ expect(data[1].options[1].value).toBe('4')
64
+ expect(data[1].options[1].text).toBe('Four')
65
+ expect(data[1].options[2].value).toBe('5')
66
+ expect(data[1].options[2].text).toBe('Five')
67
+ })
68
+
69
+ test('update select from data', () => {
70
+ document.body.innerHTML = '<select id="test"></select>'
71
+
72
+ let selectElement = document.getElementById('test') as HTMLSelectElement
73
+ let select = new Select(selectElement)
74
+ let store = new Store('single', [
75
+ {
76
+ id: '1',
77
+ value: '1',
78
+ text: 'One',
79
+ selected: false,
80
+ },
81
+ {
82
+ id: '2',
83
+ value: '2',
84
+ text: 'Two',
85
+ selected: false,
86
+ },
87
+ ])
88
+ let data = store.getData()
89
+ select.updateOptions(data)
90
+
91
+ expect(selectElement.innerHTML).toBe('<option value="1">One</option><option value="2">Two</option>')
92
+ })
93
+
94
+ test('event change value listener', () => {
95
+ document.body.innerHTML = `<select id="test">
96
+ <option value="1">One</option>
97
+ <option value="2">Two</option>
98
+ <option value="3">Three</option>
99
+ </select>`
100
+
101
+ let selectElement = document.getElementById('test') as HTMLSelectElement
102
+ let select = new Select(selectElement)
103
+
104
+ let data = select.getData() as Option[]
105
+
106
+ expect(data[0].selected).toBe(true)
107
+
108
+ let didChange = false
109
+ select.addValueChangeListener(() => {
110
+ didChange = true
111
+ })
112
+
113
+ selectElement.value = '2'
114
+
115
+ data = select.getData() as Option[]
116
+
117
+ expect(data[1].selected).toBe(true)
118
+
119
+ // Give the mutation observer time to run
120
+ setTimeout(() => {
121
+ expect(didChange).toBe(true)
122
+ }, 100)
123
+ })
124
+
125
+ test('mutation observer listener', () => {
126
+ document.body.innerHTML = `<select id="test">
127
+ <option value="1">One</option>
128
+ <option value="2">Two</option>
129
+ <option value="3">Three</option>
130
+ </select>`
131
+
132
+ let selectElement = document.getElementById('test') as HTMLSelectElement
133
+ let select = new Select(selectElement)
134
+
135
+ let data = select.getData() as Option[]
136
+
137
+ expect(data.length).toBe(3)
138
+
139
+ let didChange = false
140
+ select.addSelectChangeListener(() => {
141
+ didChange = true
142
+ })
143
+
144
+ selectElement.innerHTML = '<option value="1">One</option><option value="2">Two</option>'
145
+
146
+ data = select.getData() as Option[]
147
+
148
+ expect(data.length).toBe(2)
149
+
150
+ // Give the mutation observer time to run
151
+ setTimeout(() => {
152
+ expect(didChange).toBe(true)
153
+ }, 100)
154
+ })
155
+ })
@@ -1,149 +1,311 @@
1
- import SlimSelect from './index'
2
- import { Option, Optgroup, dataArray } from './data'
3
- import { kebabCase } from './helper'
1
+ import { generateID, kebabCase } from './helper'
2
+ import { DataArray, DataObject, Optgroup, Option } from './store'
4
3
 
5
- interface Constructor {
6
- select: HTMLSelectElement
7
- main: SlimSelect
8
- }
4
+ export default class Select {
5
+ public select: HTMLSelectElement
6
+ public listen: boolean = false
9
7
 
10
- export class Select {
11
- public element: HTMLSelectElement
12
- public main: SlimSelect
13
- public mutationObserver: MutationObserver | null
14
- public triggerMutationObserver: boolean = true
15
- constructor(info: Constructor) {
16
- this.element = info.select
17
- this.main = info.main
18
-
19
- // If original select is set to disabled lets make sure slim is too
20
- if (this.element.disabled) { this.main.config.isEnabled = false }
21
-
22
- this.addAttributes()
23
- this.addEventListeners()
24
- this.mutationObserver = null
25
- this.addMutationObserver()
26
-
27
- // Add slim to original select dropdown
28
- const el = this.element as any
29
- el.slim = info.main
30
- }
31
-
32
- public setValue(): void {
33
- if (!this.main.data.getSelected()) { return }
34
-
35
- if (this.main.config.isMultiple) {
36
- // If multiple loop through options and set selected
37
- const selected = this.main.data.getSelected() as Option[]
38
- const options = this.element.options as any as HTMLOptionElement[]
39
- for (const o of options) {
40
- o.selected = false
41
- for (const s of selected) {
42
- if (s.value === o.value) {
43
- o.selected = true
44
- }
45
- }
46
- }
8
+ // Mutation observer fields
9
+ public onSelectChange?: (data: DataArray) => void
10
+ public onValueChange?: (value: string[]) => void
11
+ private observer: MutationObserver | null = null
12
+
13
+ constructor(select: HTMLSelectElement) {
14
+ this.select = select
15
+ }
16
+
17
+ // Set to enabled
18
+ public enable(): void {
19
+ // Disable original select but dont trigger observer
20
+ this.disconnectObserver()
21
+ this.select.disabled = false
22
+ this.connectObserver()
23
+ }
24
+
25
+ // Set to disabled
26
+ public disable(): void {
27
+ // Enable original select but dont trigger observer
28
+ this.disconnectObserver()
29
+ this.select.disabled = true
30
+ this.connectObserver()
31
+ }
32
+
33
+ // Set misc attributes on the main select element
34
+ public hideUI(): void {
35
+ this.select.tabIndex = -1
36
+ this.select.style.display = 'none'
37
+ this.select.setAttribute('aria-hidden', 'true')
38
+ }
39
+
40
+ public showUI(): void {
41
+ this.select.removeAttribute('tabindex')
42
+ this.select.style.display = ''
43
+ this.select.removeAttribute('aria-hidden')
44
+ }
45
+
46
+ public changeListen(on: boolean) {
47
+ this.listen = on
48
+
49
+ // Deal with some observer situations
50
+ if (this.listen) {
51
+ this.connectObserver()
47
52
  } else {
48
- // If single select simply set value
49
- const selected = this.main.data.getSelected() as any
50
- this.element.value = (selected ? selected.value : '')
53
+ this.disconnectObserver()
51
54
  }
55
+ }
52
56
 
53
- // Do not trigger onChange callbacks for this event listener
54
- this.main.data.isOnChangeEnabled = false
55
- this.element.dispatchEvent(new CustomEvent('change', { bubbles: true }))
56
- this.main.data.isOnChangeEnabled = true
57
+ // Add change listener to original select
58
+ public addSelectChangeListener(func: (data: DataArray) => void): void {
59
+ this.onSelectChange = func
60
+ this.addObserver()
61
+ this.connectObserver()
62
+ this.changeListen(true) // Last start listening
57
63
  }
58
64
 
59
- public addAttributes() {
60
- this.element.tabIndex = -1
61
- this.element.style.display = 'none'
65
+ // remove change listener from original select
66
+ public removeSelectChangeListener(): void {
67
+ this.changeListen(false) // First stop listening
68
+ this.onSelectChange = undefined
69
+ }
62
70
 
63
- // Add slim select id
64
- this.element.dataset.ssid = this.main.config.id
65
- this.element.setAttribute('aria-hidden', 'true')
71
+ public addValueChangeListener(func: (value: string[]) => void): void {
72
+ this.onValueChange = func
73
+ this.select.addEventListener('change', this.valueChange.bind(this))
66
74
  }
67
75
 
68
- // Add onChange listener to original select
69
- public addEventListeners() {
70
- this.element.addEventListener('change', (e: Event) => {
71
- this.main.data.setSelectedFromSelect()
72
- this.main.render()
73
- })
76
+ public removeValueChangeListener(): void {
77
+ this.onValueChange = undefined
78
+ this.select.removeEventListener('change', this.valueChange.bind(this))
79
+ }
80
+
81
+ public valueChange(ev: Event): any {
82
+ if (this.onValueChange) {
83
+ this.onValueChange(this.getSelectedValues())
84
+ }
85
+ }
86
+
87
+ private observeWrapper(mutations: MutationRecord[]): void {
88
+ if (this.onSelectChange) {
89
+ this.onSelectChange(this.getData())
90
+ }
74
91
  }
75
92
 
76
93
  // Add MutationObserver to select
77
- public addMutationObserver(): void {
78
- // Only add if not in ajax mode
79
- if (this.main.config.isAjax) { return }
94
+ private addObserver(): void {
95
+ // If mutation observer already exists then disconnect and
96
+ if (this.observer) {
97
+ this.disconnectObserver()
98
+ this.observer = null
99
+ }
100
+
101
+ // If anything changes in the select then update the data
102
+ this.observer = new MutationObserver(this.observeWrapper)
103
+ }
104
+
105
+ private connectObserver(): void {
106
+ if (this.observer) {
107
+ this.observer.observe(this.select, {
108
+ attributes: true,
109
+ childList: true,
110
+ characterData: true,
111
+ subtree: true,
112
+ })
113
+ }
114
+ }
115
+
116
+ private disconnectObserver(): void {
117
+ if (this.observer) {
118
+ this.observer.disconnect()
119
+ }
120
+ }
121
+
122
+ // From the select element pull optgroup and options into data
123
+ public getData(): DataArray {
124
+ let data = []
125
+
126
+ // Loop through nodes and get data
127
+ const nodes = this.select.childNodes as any as HTMLOptGroupElement[] | HTMLOptionElement[]
128
+ for (const n of nodes) {
129
+ // Optgroup
130
+ if (n.nodeName === 'OPTGROUP') {
131
+ data.push(this.getDataFromOptgroup(n as HTMLOptGroupElement))
132
+ }
133
+
134
+ // Option
135
+ if (n.nodeName === 'OPTION') {
136
+ data.push(this.getDataFromOption(n as HTMLOptionElement))
137
+ }
138
+ }
139
+
140
+ return data
141
+ }
142
+
143
+ public getDataFromOptgroup(optgroup: HTMLOptGroupElement): Optgroup {
144
+ let data = {
145
+ id: '',
146
+ label: optgroup.label,
147
+ options: [],
148
+ } as Optgroup
80
149
 
81
- this.mutationObserver = new MutationObserver((mutations) => {
82
- if (!this.triggerMutationObserver) {return}
150
+ const options = optgroup.childNodes as any as HTMLOptionElement[]
151
+ for (const o of options) {
152
+ if (o.nodeName === 'OPTION') {
153
+ data.options.push(this.getDataFromOption(o as HTMLOptionElement))
154
+ }
155
+ }
156
+
157
+ return data
158
+ }
83
159
 
84
- this.main.data.parseSelectData()
85
- this.main.data.setSelectedFromSelect()
86
- this.main.render()
160
+ public getSelectedValues(): string[] {
161
+ let values = []
87
162
 
88
- mutations.forEach((mutation) => {
89
- if (mutation.attributeName === 'class') {
90
- this.main.slim.updateContainerDivClass(this.main.slim.container)
163
+ // Loop through options and set selected
164
+ const options = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
165
+ for (const o of options) {
166
+ if (o.nodeName === 'OPTGROUP') {
167
+ const optgroupOptions = o.childNodes as any as HTMLOptionElement[]
168
+ for (const oo of optgroupOptions) {
169
+ if (oo.nodeName === 'OPTION') {
170
+ const option = oo as HTMLOptionElement
171
+ if (option.selected) {
172
+ values.push(option.value)
173
+ }
174
+ }
91
175
  }
92
- })
93
- })
176
+ }
177
+
178
+ if (o.nodeName === 'OPTION') {
179
+ const option = o as HTMLOptionElement
180
+ if (option.selected) {
181
+ values.push(option.value)
182
+ }
183
+ }
184
+ }
94
185
 
95
- this.observeMutationObserver()
186
+ return values
96
187
  }
97
188
 
98
- public observeMutationObserver(): void {
99
- if (!this.mutationObserver) { return }
189
+ // From passed in option pull pieces of usable information
190
+ public getDataFromOption(option: HTMLOptionElement): Option {
191
+ return {
192
+ id: (option.dataset ? option.dataset.id : false) || generateID(),
193
+ value: option.value,
194
+ text: option.text,
195
+ html: option.innerHTML,
196
+ selected: option.selected,
197
+ display: option.style.display === 'none' ? false : true,
198
+ disabled: option.disabled,
199
+ mandatory: option.dataset ? option.dataset.mandatory === 'true' : false,
200
+ placeholder: option.dataset.placeholder === 'true',
201
+ class: option.className,
202
+ style: option.style.cssText,
203
+ data: option.dataset,
204
+ } as Option
205
+ }
206
+
207
+ public setSelected(value: string[]): void {
208
+ // Loop through options and set selected
209
+ const options = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
210
+ for (const o of options) {
211
+ if (o.nodeName === 'OPTGROUP') {
212
+ const optgroup = o as HTMLOptGroupElement
213
+ const optgroupOptions = optgroup.childNodes as any as HTMLOptionElement[]
214
+ for (const oo of optgroupOptions) {
215
+ if (oo.nodeName === 'OPTION') {
216
+ const option = oo as HTMLOptionElement
217
+ option.selected = value.includes(option.value)
218
+ }
219
+ }
220
+ }
100
221
 
101
- this.mutationObserver.observe(this.element, {
102
- attributes: true,
103
- childList: true,
104
- characterData: true
105
- })
222
+ if (o.nodeName === 'OPTION') {
223
+ const option = o as HTMLOptionElement
224
+ option.selected = value.includes(option.value)
225
+ }
226
+ }
106
227
  }
107
228
 
108
- public disconnectMutationObserver(): void {
109
- if (this.mutationObserver) {
110
- this.mutationObserver.disconnect()
229
+ public updateSelect(id?: string, style?: string, classes?: string[]): void {
230
+ // Stop listening to changes
231
+ this.changeListen(false)
232
+
233
+ // Update id
234
+ if (id) {
235
+ this.select.id = id
111
236
  }
237
+
238
+ // Update style
239
+ if (style) {
240
+ this.select.style.cssText = style
241
+ }
242
+
243
+ // Update classes
244
+ if (classes) {
245
+ this.select.className = ''
246
+ classes.forEach((c) => {
247
+ if (c.trim() !== '') {
248
+ this.select.classList.add(c.trim())
249
+ }
250
+ })
251
+ }
252
+
253
+ // Start listening to changes
254
+ this.changeListen(true)
112
255
  }
113
256
 
114
- // Create select element and optgroup/options
115
- public create(data: dataArray): void {
257
+ public updateOptions(data: DataArray): void {
258
+ // Stop listening to changes
259
+ this.changeListen(false)
260
+
116
261
  // Clear out select
117
- this.element.innerHTML = ''
262
+ this.select.innerHTML = ''
118
263
 
119
264
  for (const d of data) {
120
- if (d.hasOwnProperty('options')) {
121
- const optgroupObject = d as Optgroup
122
- const optgroupEl = document.createElement('optgroup') as HTMLOptGroupElement
123
- optgroupEl.label = optgroupObject.label
124
- if (optgroupObject.options) {
125
- for (const oo of optgroupObject.options) {
126
- optgroupEl.appendChild(this.createOption(oo))
127
- }
128
- }
129
- this.element.appendChild(optgroupEl)
130
- } else {
131
- this.element.appendChild(this.createOption(d))
265
+ if (d instanceof Optgroup) {
266
+ this.select.appendChild(this.createOptgroup(d))
267
+ }
268
+
269
+ if (d instanceof Option) {
270
+ this.select.appendChild(this.createOption(d))
132
271
  }
133
272
  }
273
+
274
+ // Start listening to changes
275
+ this.changeListen(true)
134
276
  }
135
277
 
136
- public createOption(info: any): HTMLOptionElement {
278
+ public createOptgroup(optgroup: Optgroup): HTMLOptGroupElement {
279
+ const optgroupEl = document.createElement('optgroup')
280
+ optgroupEl.id = optgroup.id
281
+ optgroupEl.label = optgroup.label
282
+ if (optgroup.options) {
283
+ for (const o of optgroup.options) {
284
+ optgroupEl.appendChild(this.createOption(o))
285
+ }
286
+ }
287
+ return optgroupEl
288
+ }
289
+
290
+ public createOption(info: Option): HTMLOptionElement {
137
291
  const optionEl = document.createElement('option')
138
292
  optionEl.value = info.value !== '' ? info.value : info.text
139
- optionEl.innerHTML = info.innerHTML || info.text
140
- if (info.selected) { optionEl.selected = info.selected }
293
+ optionEl.innerHTML = info.html || info.text
294
+ if (info.selected) {
295
+ optionEl.selected = info.selected
296
+ }
297
+ if (info.disabled) {
298
+ optionEl.disabled = true
299
+ }
141
300
  if (info.display === false) {
142
301
  optionEl.style.display = 'none'
143
302
  }
144
- if (info.disabled) { optionEl.disabled = true }
145
- if (info.placeholder) { optionEl.setAttribute('data-placeholder', 'true') }
146
- if (info.mandatory) { optionEl.setAttribute('data-mandatory', 'true') }
303
+ if (info.placeholder) {
304
+ optionEl.setAttribute('data-placeholder', 'true')
305
+ }
306
+ if (info.mandatory) {
307
+ optionEl.setAttribute('data-mandatory', 'true')
308
+ }
147
309
  if (info.class) {
148
310
  info.class.split(' ').forEach((optionClass: string) => {
149
311
  optionEl.classList.add(optionClass)
@@ -157,4 +319,14 @@ export class Select {
157
319
 
158
320
  return optionEl
159
321
  }
322
+
323
+ public destroy() {
324
+ this.changeListen(false)
325
+ this.disconnectObserver()
326
+ this.removeSelectChangeListener()
327
+ this.removeValueChangeListener()
328
+
329
+ // show the original select
330
+ this.showUI()
331
+ }
160
332
  }