slim-select 3.4.3 → 3.5.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 (73) hide show
  1. package/dist/classes.d.ts +46 -0
  2. package/dist/helpers.d.ts +5 -0
  3. package/dist/index.d.ts +53 -319
  4. package/dist/index.umd.d.ts +2 -0
  5. package/dist/react/index.d.ts +4 -319
  6. package/dist/react/index.js +1459 -1697
  7. package/dist/react/index.umd.js +2 -2
  8. package/dist/react/react.d.ts +17 -0
  9. package/dist/react/react.test.d.ts +1 -0
  10. package/dist/react/slim-select/react/index.d.ts +4 -0
  11. package/dist/render.d.ts +88 -0
  12. package/dist/select.d.ts +38 -0
  13. package/dist/settings.d.ts +39 -0
  14. package/dist/slimselect.cjs.js +2 -2
  15. package/dist/slimselect.cjs.js.map +1 -1
  16. package/dist/slimselect.css +1 -1
  17. package/dist/slimselect.css.map +1 -1
  18. package/dist/slimselect.es.js +1233 -1385
  19. package/dist/slimselect.es.js.map +1 -1
  20. package/dist/slimselect.iife.js +2 -2
  21. package/dist/slimselect.iife.js.map +1 -1
  22. package/dist/slimselect.js +2 -2
  23. package/dist/slimselect.js.map +1 -1
  24. package/dist/src/docs/main.d.ts +0 -0
  25. package/dist/src/docs/router.d.ts +2 -0
  26. package/dist/src/docs/store.d.ts +12 -0
  27. package/dist/store.d.ts +53 -0
  28. package/dist/vue/index.d.ts +4 -319
  29. package/dist/vue/index.js +1335 -1504
  30. package/dist/vue/index.umd.js +1 -1
  31. package/dist/vue/slim-select/vue/index.d.ts +4 -0
  32. package/package.json +30 -36
  33. package/src/slim-select/slimselect.scss +34 -5
  34. package/.nvmrc +0 -1
  35. package/.prettierignore +0 -1
  36. package/.prettierrc +0 -7
  37. package/CODE_OF_CONDUCT.md +0 -46
  38. package/CONTRIBUTING.md +0 -1
  39. package/cdn.html +0 -21
  40. package/docs.go +0 -12
  41. package/example.html +0 -25
  42. package/index.html +0 -76
  43. package/release.js +0 -191
  44. package/sitemap.js +0 -108
  45. package/src/slim-select/accessibility.test.ts +0 -782
  46. package/src/slim-select/classes.test.ts +0 -73
  47. package/src/slim-select/classes.ts +0 -117
  48. package/src/slim-select/helpers.test.ts +0 -162
  49. package/src/slim-select/helpers.ts +0 -61
  50. package/src/slim-select/index.test.ts +0 -1356
  51. package/src/slim-select/index.ts +0 -594
  52. package/src/slim-select/index.umd.ts +0 -8
  53. package/src/slim-select/react/index.tsx +0 -9
  54. package/src/slim-select/react/react.test.tsx +0 -498
  55. package/src/slim-select/react/react.tsx +0 -209
  56. package/src/slim-select/render.test.ts +0 -1927
  57. package/src/slim-select/render.ts +0 -1771
  58. package/src/slim-select/select.test.ts +0 -683
  59. package/src/slim-select/select.ts +0 -659
  60. package/src/slim-select/settings.test.ts +0 -285
  61. package/src/slim-select/settings.ts +0 -78
  62. package/src/slim-select/store.test.ts +0 -1007
  63. package/src/slim-select/store.ts +0 -433
  64. package/src/slim-select/vue/index.ts +0 -9
  65. package/src/slim-select/vue/vue.test.ts +0 -1091
  66. package/src/slim-select/vue/vue.vue +0 -224
  67. package/tsconfig.json +0 -26
  68. package/vite.config.docs.mts +0 -29
  69. package/vite.config.lib.es-cjs.ts +0 -40
  70. package/vite.config.lib.umd-iife.ts +0 -34
  71. package/vite.config.react.mts +0 -41
  72. package/vite.config.vue.mts +0 -40
  73. package/vitest.config.ts +0 -30
@@ -1,659 +0,0 @@
1
- import { kebabCase, hasClassInTree } from './helpers'
2
- import { Optgroup, Option } from './store'
3
-
4
- export default class Select {
5
- public select: HTMLSelectElement
6
-
7
- // Mutation observer fields
8
- public onValueChange?: (value: Option[]) => void
9
- public onClassChange?: (classes: string[]) => void
10
- public onDisabledChange?: (disabled: boolean) => void
11
- public onOptionsChange?: (data: (Option | Optgroup)[]) => void
12
- public onLabelClick?: () => void
13
-
14
- // Change observers
15
- private listen: boolean = false
16
- private observer: MutationObserver | null = null
17
- private isUpdating: boolean = false
18
- private pendingOptionsChange: (Option | Optgroup)[] | null = null
19
-
20
- // Event handlers for preventing native select behavior (especially on iOS Safari)
21
- private preventNativeSelect: ((e: Event) => void) | null = null
22
- private preventNativeSelectMousedown: ((e: Event) => void) | null = null
23
- private preventNativeSelectFocus: ((e: Event) => void) | null = null
24
-
25
- constructor(select: HTMLSelectElement) {
26
- this.select = select
27
-
28
- this.valueChange = this.valueChange.bind(this)
29
- // Add change event listener
30
- this.select.addEventListener('change', this.valueChange, {
31
- // allow bubbling of event
32
- passive: true
33
- })
34
-
35
- // Initiate mutation observer
36
- this.observer = new MutationObserver(this.observeCall.bind(this))
37
-
38
- // Start listening for changes
39
- this.changeListen(true)
40
- }
41
-
42
- public enable(): void {
43
- this.select.disabled = false
44
- }
45
-
46
- public disable(): void {
47
- this.select.disabled = true
48
- }
49
-
50
- public hideUI(): void {
51
- this.select.tabIndex = -1
52
- // Visually hide but keep focusable for form validation
53
- // Use 1px dimensions so browser validation popup can display
54
- this.select.style.position = 'absolute'
55
- this.select.style.width = '1px'
56
- this.select.style.height = '1px'
57
- this.select.style.opacity = '0'
58
- this.select.style.overflow = 'hidden'
59
- this.select.style.pointerEvents = 'none'
60
- // Remove any spacing or borders that could affect layout
61
- this.select.style.margin = '0'
62
- this.select.style.padding = '0'
63
- this.select.style.borderWidth = '0'
64
- // Clip to completely hide the 1px
65
- this.select.style.clip = 'rect(0 0 0 0)'
66
- this.select.setAttribute('aria-hidden', 'true')
67
-
68
- // Prevent native select from opening when label is clicked (especially on iOS Safari)
69
- // iOS Safari programmatically focuses/clicks the select when label is clicked,
70
- // bypassing pointer-events: none, so we need to prevent these events
71
- // Only add listeners if they haven't been added yet
72
- if (!this.preventNativeSelect) {
73
- this.preventNativeSelect = (e: Event) => {
74
- e.preventDefault()
75
- e.stopPropagation()
76
- e.stopImmediatePropagation()
77
- }
78
-
79
- this.preventNativeSelectMousedown = (e: Event) => {
80
- e.preventDefault()
81
- e.stopPropagation()
82
- e.stopImmediatePropagation()
83
- }
84
-
85
- this.preventNativeSelectFocus = (e: Event) => {
86
- e.preventDefault()
87
- e.stopPropagation()
88
- e.stopImmediatePropagation()
89
- }
90
-
91
- // Add event listeners with capture phase to catch events before they reach the select
92
- this.select.addEventListener('click', this.preventNativeSelect, { capture: true, passive: false })
93
- this.select.addEventListener('mousedown', this.preventNativeSelectMousedown, { capture: true, passive: false })
94
- this.select.addEventListener('focus', this.preventNativeSelectFocus, { capture: true, passive: false })
95
- }
96
- }
97
-
98
- public showUI(): void {
99
- this.select.removeAttribute('tabindex')
100
- // Reset visual hiding styles
101
- this.select.style.position = ''
102
- this.select.style.width = ''
103
- this.select.style.height = ''
104
- this.select.style.opacity = ''
105
- this.select.style.overflow = ''
106
- this.select.style.pointerEvents = ''
107
- this.select.style.margin = ''
108
- this.select.style.padding = ''
109
- this.select.style.borderWidth = ''
110
- this.select.style.clip = ''
111
- this.select.removeAttribute('aria-hidden')
112
-
113
- // Remove event listeners that prevent native select behavior
114
- if (this.preventNativeSelect) {
115
- this.select.removeEventListener('click', this.preventNativeSelect, { capture: true })
116
- this.preventNativeSelect = null
117
- }
118
- if (this.preventNativeSelectMousedown) {
119
- this.select.removeEventListener('mousedown', this.preventNativeSelectMousedown, { capture: true })
120
- this.preventNativeSelectMousedown = null
121
- }
122
- if (this.preventNativeSelectFocus) {
123
- this.select.removeEventListener('focus', this.preventNativeSelectFocus, { capture: true })
124
- this.preventNativeSelectFocus = null
125
- }
126
- }
127
-
128
- public changeListen(listen: boolean) {
129
- this.listen = listen
130
-
131
- // Start listening for changes
132
- if (listen) {
133
- if (this.observer) {
134
- this.observer.observe(this.select, {
135
- subtree: true, // subtree for optgroups options
136
- childList: true, // children changes
137
- attributes: true // attributes changes
138
- })
139
- }
140
- }
141
-
142
- // Stop listening for changes
143
- if (!listen) {
144
- if (this.observer) {
145
- this.observer.disconnect()
146
- }
147
- }
148
- }
149
-
150
- // This function get triggers when the select value changes
151
- // and will call the onValueChange function if it exists
152
- public valueChange(ev: Event): boolean {
153
- if (this.listen && this.onValueChange) {
154
- this.onValueChange(this.getSelectedOptions())
155
- }
156
-
157
- // Allow bubbling back to other change event listeners
158
- return true
159
- }
160
-
161
- private observeCall(mutations: MutationRecord[]): void {
162
- // If we are not listening, do nothing.
163
- if (!this.listen) {
164
- return
165
- }
166
-
167
- let classChanged = false
168
- let disabledChanged = false
169
- let optgroupOptionChanged = false
170
- let selectionChanged = false
171
-
172
- // Loop through mutations and check various things
173
- for (const m of mutations) {
174
- // Check if its the select
175
- if (m.target === this.select) {
176
- // Check if disabled has changed
177
- if (m.attributeName === 'disabled') {
178
- disabledChanged = true
179
- }
180
-
181
- // Check if class has changed
182
- if (m.attributeName === 'class') {
183
- classChanged = true
184
- }
185
-
186
- if (m.type === 'childList') {
187
- for (const n of Array.from(m.addedNodes)) {
188
- if (n.nodeName === 'OPTION' && (<HTMLOptionElement>n).value === this.select.value) {
189
- // we added a new option that's now the select value
190
- selectionChanged = true
191
- break
192
- }
193
- }
194
-
195
- // options changed, so we need the optionsChange event to fire
196
- optgroupOptionChanged = true
197
- }
198
- }
199
-
200
- // Check if its an optgroup or option
201
- if (m.target.nodeName === 'OPTGROUP' || m.target.nodeName === 'OPTION') {
202
- optgroupOptionChanged = true
203
- }
204
- }
205
-
206
- // If class has changed then call the class change function
207
- if (classChanged && this.onClassChange) {
208
- this.onClassChange(this.select.className.split(' '))
209
- }
210
-
211
- // If disabled has changed then call the disabled change function
212
- if (disabledChanged && this.onDisabledChange) {
213
- this.changeListen(false)
214
- this.onDisabledChange(this.select.disabled)
215
- this.changeListen(true)
216
- }
217
-
218
- // If optgroup or option has changed then call the select change function
219
- if (optgroupOptionChanged && this.onOptionsChange) {
220
- // If we're currently updating, queue this change to process after update completes
221
- if (this.isUpdating) {
222
- // Only queue if the select element actually has options (not empty from updateOptions clearing)
223
- // Check the select element directly, not getData() which might return stale data
224
- if (this.select.options.length > 0) {
225
- const currentData = this.getData()
226
- // Only queue if we have actual data
227
- if (currentData.length > 0) {
228
- this.pendingOptionsChange = currentData
229
- }
230
- }
231
- if (selectionChanged) {
232
- this.select.dispatchEvent(new Event('change'))
233
- }
234
- return
235
- }
236
-
237
- this.changeListen(false)
238
- this.onOptionsChange(this.getData())
239
- this.changeListen(true)
240
- }
241
-
242
- if (selectionChanged) {
243
- this.select.dispatchEvent(new Event('change'))
244
- }
245
- }
246
-
247
- // From the select element pull optgroup and options into data
248
- public getData(): (Option | Optgroup)[] {
249
- let data = []
250
-
251
- // Loop through nodes and get data
252
- const nodes = this.select.childNodes as any as HTMLOptGroupElement[] | HTMLOptionElement[]
253
- for (const n of nodes) {
254
- // Optgroup
255
- if (n.nodeName === 'OPTGROUP') {
256
- data.push(this.getDataFromOptgroup(n as HTMLOptGroupElement))
257
- }
258
-
259
- // Option
260
- if (n.nodeName === 'OPTION') {
261
- data.push(this.getDataFromOption(n as HTMLOptionElement))
262
- }
263
- }
264
-
265
- return data
266
- }
267
-
268
- public getDataFromOptgroup(optgroup: HTMLOptGroupElement): Optgroup {
269
- let data = {
270
- id: optgroup.id,
271
- label: optgroup.label,
272
- selectAll: optgroup.dataset ? optgroup.dataset.selectall === 'true' : false,
273
- selectAllText: optgroup.dataset ? optgroup.dataset.selectalltext : 'Select all',
274
- closable: optgroup.dataset ? optgroup.dataset.closable : 'off',
275
- options: []
276
- } as Optgroup
277
-
278
- const options = optgroup.childNodes as any as HTMLOptionElement[]
279
- for (const o of options) {
280
- if (o.nodeName === 'OPTION') {
281
- data.options!.push(this.getDataFromOption(o as HTMLOptionElement))
282
- }
283
- }
284
-
285
- return data
286
- }
287
-
288
- // From passed in option pull pieces of usable information
289
- public getDataFromOption(option: HTMLOptionElement): Option {
290
- return {
291
- id: option.id,
292
- value: option.value,
293
- text: option.text,
294
- html: option.dataset && option.dataset.html ? option.dataset.html : '',
295
- defaultSelected: option.defaultSelected,
296
- selected: option.selected,
297
- display: option.style.display !== 'none',
298
- disabled: option.disabled,
299
- mandatory: option.dataset ? option.dataset.mandatory === 'true' : false,
300
- placeholder: option.dataset.placeholder === 'true',
301
- class: option.className,
302
- style: option.style.cssText,
303
- data: option.dataset
304
- } as Option
305
- }
306
-
307
- public getSelectedOptions(): Option[] {
308
- let options = []
309
-
310
- // Loop through options and set selected
311
- const opts = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
312
- for (const o of opts) {
313
- if (o.nodeName === 'OPTGROUP') {
314
- const optgroupOptions = o.childNodes as any as HTMLOptionElement[]
315
- for (const oo of optgroupOptions) {
316
- if (oo.nodeName === 'OPTION') {
317
- const option = oo as HTMLOptionElement
318
- if (option.selected) {
319
- options.push(this.getDataFromOption(option))
320
- }
321
- }
322
- }
323
- }
324
-
325
- if (o.nodeName === 'OPTION') {
326
- const option = o as HTMLOptionElement
327
- if (option.selected) {
328
- options.push(this.getDataFromOption(option))
329
- }
330
- }
331
- }
332
-
333
- return options
334
- }
335
-
336
- public getSelectedValues(): string[] {
337
- return this.getSelectedOptions().map((option) => option.value)
338
- }
339
-
340
- public setSelected(ids: string[]): void {
341
- // Stop listening to changes
342
- this.changeListen(false)
343
-
344
- // Loop through options and set selected
345
- const options = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
346
- for (const o of options) {
347
- if (o.nodeName === 'OPTGROUP') {
348
- const optgroup = o as HTMLOptGroupElement
349
- const optgroupOptions = optgroup.childNodes as any as HTMLOptionElement[]
350
- for (const oo of optgroupOptions) {
351
- if (oo.nodeName === 'OPTION') {
352
- const option = oo as HTMLOptionElement
353
- option.selected = ids.includes(option.id)
354
- }
355
- }
356
- }
357
-
358
- if (o.nodeName === 'OPTION') {
359
- const option = o as HTMLOptionElement
360
- option.selected = ids.includes(option.id)
361
- }
362
- }
363
-
364
- // Stop listening to changes
365
- this.changeListen(true)
366
- }
367
-
368
- // Set selected options by value instead of id
369
- // This is useful when the id is not known
370
- // and only the value is known
371
- // but the value is not unique and can be duplicated
372
- public setSelectedByValue(values: string[]): void {
373
- // Stop listening to changes
374
- this.changeListen(false)
375
-
376
- // Loop through options and set selected
377
- const options = this.select.childNodes as any as (HTMLOptGroupElement | HTMLOptionElement)[]
378
- for (const o of options) {
379
- if (o.nodeName === 'OPTGROUP') {
380
- const optgroup = o as HTMLOptGroupElement
381
- const optgroupOptions = optgroup.childNodes as any as HTMLOptionElement[]
382
- for (const oo of optgroupOptions) {
383
- if (oo.nodeName === 'OPTION') {
384
- const option = oo as HTMLOptionElement
385
- option.selected = values.includes(option.value)
386
- }
387
- }
388
- }
389
-
390
- if (o.nodeName === 'OPTION') {
391
- const option = o as HTMLOptionElement
392
- option.selected = values.includes(option.value)
393
- }
394
- }
395
-
396
- // Stop listening to changes
397
- this.changeListen(true)
398
- }
399
-
400
- public updateSelect(id?: string, style?: string, classes?: string[]): void {
401
- // Stop listening to changes
402
- this.changeListen(false)
403
-
404
- // Update id, only if the id isnt already set
405
- if (id) {
406
- this.select.dataset.id = id
407
- }
408
-
409
- // Update style
410
- if (style) {
411
- this.select.style.cssText = style
412
- }
413
-
414
- // Update classes
415
- if (classes) {
416
- this.select.className = ''
417
- classes.forEach((c) => {
418
- if (c.trim() !== '') {
419
- this.select.classList.add(c.trim())
420
- }
421
- })
422
- }
423
-
424
- // Start listening to changes
425
- this.changeListen(true)
426
- }
427
-
428
- public updateOptions(data: (Option | Optgroup)[]): void {
429
- // Don't update if data is empty (prevents clearing select when no data)
430
- if (!data || data.length === 0) {
431
- return
432
- }
433
-
434
- // Set updating flag FIRST to prevent mutation observer from processing during update
435
- // This must be set before stopping listening so any queued mutations see the flag
436
- this.isUpdating = true
437
- this.pendingOptionsChange = null
438
-
439
- // Stop listening to changes
440
- this.changeListen(false)
441
-
442
- // Clear out select
443
- this.select.innerHTML = ''
444
-
445
- for (const d of data) {
446
- if (d instanceof Optgroup) {
447
- this.select.appendChild(this.createOptgroup(d))
448
- }
449
-
450
- if (d instanceof Option) {
451
- this.select.appendChild(this.createOption(d))
452
- }
453
- }
454
-
455
- // Trigger change event on original select
456
- this.select.dispatchEvent(new Event('change', { bubbles: true }))
457
-
458
- // Start listening to changes
459
- this.changeListen(true)
460
-
461
- // Clear updating flag
462
- this.isUpdating = false
463
-
464
- // Process any pending options change that was queued during the update
465
- // Only process if it has data (don't process empty data from innerHTML clearing)
466
- if (this.pendingOptionsChange !== null) {
467
- const pending: (Option | Optgroup)[] = this.pendingOptionsChange
468
- if (pending.length > 0 && this.onOptionsChange) {
469
- this.pendingOptionsChange = null
470
- // Process the queued change
471
- this.changeListen(false)
472
- this.onOptionsChange(pending)
473
- this.changeListen(true)
474
- } else {
475
- // Clear pending if it was empty
476
- this.pendingOptionsChange = null
477
- }
478
- }
479
- }
480
-
481
- public createOptgroup(optgroup: Optgroup): HTMLOptGroupElement {
482
- const optgroupEl = document.createElement('optgroup')
483
- optgroupEl.id = optgroup.id
484
- optgroupEl.label = optgroup.label
485
- if (optgroup.selectAll) {
486
- optgroupEl.dataset.selectAll = 'true'
487
- }
488
- if (optgroup.closable !== 'off') {
489
- optgroupEl.dataset.closable = optgroup.closable
490
- }
491
- if (optgroup.options) {
492
- for (const option of optgroup.options) {
493
- optgroupEl.appendChild(this.createOption(option as Option))
494
- }
495
- }
496
- return optgroupEl
497
- }
498
-
499
- public createOption(info: Option): HTMLOptionElement {
500
- const optionEl = document.createElement('option')
501
- optionEl.id = info.id
502
- optionEl.value = info.value
503
- optionEl.textContent = info.text
504
- if (info.html !== '') {
505
- optionEl.setAttribute('data-html', info.html)
506
- }
507
- optionEl.defaultSelected = info.defaultSelected
508
- optionEl.selected = info.selected // Explicitly set selected because defaultSelected: true sets selected to true
509
- if (info.disabled) {
510
- optionEl.disabled = true
511
- }
512
- if (!info.display) {
513
- optionEl.style.display = 'none'
514
- }
515
- if (info.placeholder) {
516
- optionEl.setAttribute('data-placeholder', 'true')
517
- }
518
- if (info.mandatory) {
519
- optionEl.setAttribute('data-mandatory', 'true')
520
- }
521
- if (info.class) {
522
- info.class.split(' ').forEach((optionClass: string) => {
523
- optionEl.classList.add(optionClass)
524
- })
525
- }
526
- if (info.data && typeof info.data === 'object') {
527
- Object.keys(info.data).forEach((key) => {
528
- optionEl.setAttribute('data-' + kebabCase(key), info.data[key])
529
- })
530
- }
531
-
532
- return optionEl
533
- }
534
-
535
- // Find and handle labels associated with this select
536
- public setupLabelHandlers(): void {
537
- const labels: HTMLLabelElement[] = []
538
-
539
- // Find labels that point to this select via 'for' attribute
540
- const selectId = this.select.id
541
- if (selectId) {
542
- const labelsByFor = document.querySelectorAll<HTMLLabelElement>(`label[for="${selectId}"]`)
543
- labelsByFor.forEach((label) => labels.push(label))
544
- }
545
-
546
- // Find labels that wrap this select (label contains select)
547
- let parent = this.select.parentElement
548
- while (parent && parent !== document.body) {
549
- if (parent.tagName === 'LABEL') {
550
- labels.push(parent as HTMLLabelElement)
551
- break
552
- }
553
- parent = parent.parentElement
554
- }
555
-
556
- // Remove duplicates
557
- const uniqueLabels = Array.from(new Set(labels))
558
-
559
- uniqueLabels.forEach((label) => {
560
- // Skip if already handled
561
- if ((label as any).__slimSelectLabelHandler) {
562
- return
563
- }
564
-
565
- // Add click handler to label
566
- const labelClickHandler = (e: MouseEvent) => {
567
- const target = e.target as HTMLElement
568
-
569
- // Check if click is on SlimSelect UI elements (main div or content)
570
- const isSlimSelectElement = hasClassInTree(target, this.select.dataset.id!)
571
-
572
- // Prevent default label behavior (focusing the select)
573
- // This needs to happen for all clicks on the label or its children
574
- // to prevent the browser from focusing the hidden select
575
- e.preventDefault()
576
- // e.stopPropagation() // dont stop propagation
577
-
578
- // Only trigger the toggle if the click is NOT on SlimSelect elements
579
- // This allows clicking label text/children to toggle, while wrapped SlimSelect handles its own clicks
580
- if (!isSlimSelectElement && this.onLabelClick) {
581
- this.onLabelClick()
582
- }
583
- }
584
-
585
- // Store the handler on the label for cleanup later
586
- ;(label as any).__slimSelectLabelHandler = labelClickHandler
587
- label.addEventListener('click', labelClickHandler, { capture: true, passive: false })
588
- })
589
- }
590
-
591
- // Remove label handlers
592
- public removeLabelHandlers(): void {
593
- const labels: HTMLLabelElement[] = []
594
-
595
- // Find labels that point to this select via 'for' attribute
596
- const selectId = this.select.id
597
- if (selectId) {
598
- const labelsByFor = document.querySelectorAll<HTMLLabelElement>(`label[for="${selectId}"]`)
599
- labelsByFor.forEach((label) => labels.push(label))
600
- }
601
-
602
- // Find labels that wrap this select
603
- let parent = this.select.parentElement
604
- while (parent && parent !== document.body) {
605
- if (parent.tagName === 'LABEL') {
606
- labels.push(parent as HTMLLabelElement)
607
- break
608
- }
609
- parent = parent.parentElement
610
- }
611
-
612
- // Remove duplicates
613
- const uniqueLabels = Array.from(new Set(labels))
614
-
615
- uniqueLabels.forEach((label) => {
616
- const handler = (label as any).__slimSelectLabelHandler
617
- if (handler) {
618
- label.removeEventListener('click', handler, { capture: true })
619
- delete (label as any).__slimSelectLabelHandler
620
- }
621
- })
622
- }
623
-
624
- public destroy() {
625
- this.changeListen(false)
626
-
627
- // Remove event change listener
628
- this.select.removeEventListener('change', this.valueChange)
629
-
630
- // Remove event listeners that prevent native select behavior
631
- if (this.preventNativeSelect) {
632
- this.select.removeEventListener('click', this.preventNativeSelect, { capture: true })
633
- this.preventNativeSelect = null
634
- }
635
- if (this.preventNativeSelectMousedown) {
636
- this.select.removeEventListener('mousedown', this.preventNativeSelectMousedown, { capture: true })
637
- this.preventNativeSelectMousedown = null
638
- }
639
- if (this.preventNativeSelectFocus) {
640
- this.select.removeEventListener('focus', this.preventNativeSelectFocus, { capture: true })
641
- this.preventNativeSelectFocus = null
642
- }
643
-
644
- // Disconnect observer and null
645
- if (this.observer) {
646
- this.observer.disconnect()
647
- this.observer = null
648
- }
649
-
650
- // Remove label handlers
651
- this.removeLabelHandlers()
652
-
653
- // Remove dataset id from original select
654
- delete this.select.dataset.id
655
-
656
- // Show the original select
657
- this.showUI()
658
- }
659
- }