slim-select 2.13.0 → 3.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.
- package/README.md +184 -43
- package/dist/index.d.ts +304 -52
- package/dist/react/index.d.ts +304 -0
- package/dist/react/index.js +1549 -0
- package/dist/react/index.umd.js +22 -0
- package/dist/slimselect.cjs.js +1 -2030
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +1224 -2012
- package/dist/slimselect.umd.js +1 -2036
- package/dist/vue/index.d.ts +304 -0
- package/dist/vue/index.js +1341 -0
- package/dist/vue/index.umd.js +1 -0
- package/index.html +1 -0
- package/package.json +59 -37
- package/release.js +0 -7
- package/src/slim-select/accessibility.test.ts +782 -0
- package/src/slim-select/classes.test.ts +7 -6
- package/src/slim-select/classes.ts +8 -6
- package/src/slim-select/helpers.test.ts +10 -8
- package/src/slim-select/index.test.ts +509 -14
- package/src/slim-select/index.ts +58 -31
- package/src/slim-select/react/index.tsx +9 -0
- package/src/slim-select/react/react.tsx +112 -0
- package/src/slim-select/render.test.ts +479 -119
- package/src/slim-select/render.ts +213 -91
- package/src/slim-select/select.test.ts +28 -10
- package/src/slim-select/select.ts +35 -12
- package/src/slim-select/settings.test.ts +19 -8
- package/src/slim-select/settings.ts +2 -4
- package/src/slim-select/slimselect.scss +30 -15
- package/src/slim-select/store.test.ts +13 -16
- package/src/slim-select/store.ts +97 -86
- package/src/slim-select/vue/index.ts +9 -0
- package/src/slim-select/vue/vue.test.ts +832 -0
- package/src/slim-select/{vue.vue → vue/vue.vue} +45 -23
- package/tsconfig.json +6 -4
- package/{vite.config.mts → vite.config.docs.mts} +10 -0
- package/vite.config.lib.mts +42 -0
- package/vite.config.react.mts +40 -0
- package/vite.config.vue.mts +39 -0
- package/vitest.config.ts +29 -0
- package/dist/classes.d.ts +0 -43
- package/dist/helpers.d.ts +0 -5
- package/dist/render.d.ts +0 -81
- package/dist/select.d.ts +0 -30
- package/dist/settings.d.ts +0 -35
- package/dist/slimselect.global.js +0 -2033
- package/dist/slimselect.js +0 -2036
- package/dist/slimselect.min.js +0 -1
- package/dist/slimselect.umd.min.js +0 -1
- package/dist/store.d.ts +0 -84
- package/jest.config.js +0 -14
- package/src/slim-select/rollup.config.mjs +0 -127
- package/src/slim-select/tsconfig.json +0 -14
package/src/slim-select/index.ts
CHANGED
|
@@ -3,26 +3,28 @@ import { debounce, hasClassInTree, isEqual } from './helpers'
|
|
|
3
3
|
import Render from './render'
|
|
4
4
|
import Select from './select'
|
|
5
5
|
import Settings from './settings'
|
|
6
|
-
import
|
|
7
|
-
import Store from './store'
|
|
8
|
-
import type { DataArray, DataArrayPartial, Option, OptionOptional } from './store'
|
|
6
|
+
import Store, { Option, Optgroup } from './store'
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
// Export classes
|
|
9
|
+
export { Select, Settings, Render, Store, Option, Optgroup }
|
|
11
10
|
|
|
12
11
|
export interface Config {
|
|
13
12
|
select: string | Element
|
|
14
|
-
data?:
|
|
15
|
-
settings?:
|
|
13
|
+
data?: (Partial<Option> | Partial<Optgroup>)[]
|
|
14
|
+
settings?: Partial<Settings>
|
|
16
15
|
cssClasses?: Partial<CssClasses>
|
|
17
16
|
events?: Events
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface Events {
|
|
21
|
-
search?: (
|
|
20
|
+
search?: (
|
|
21
|
+
searchValue: string,
|
|
22
|
+
currentData: (Option | Optgroup)[]
|
|
23
|
+
) => Promise<(Partial<Option> | Partial<Optgroup>)[]> | (Partial<Option> | Partial<Optgroup>)[]
|
|
22
24
|
searchFilter?: (option: Option, search: string) => boolean
|
|
23
25
|
addable?: (
|
|
24
26
|
value: string
|
|
25
|
-
) => Promise<
|
|
27
|
+
) => Promise<Partial<Option> | string> | Partial<Option> | string | false | null | undefined | Error
|
|
26
28
|
beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void
|
|
27
29
|
afterChange?: (newVal: Option[]) => void
|
|
28
30
|
beforeOpen?: () => void
|
|
@@ -42,6 +44,10 @@ export default class SlimSelect {
|
|
|
42
44
|
public store!: Store
|
|
43
45
|
public render!: Render
|
|
44
46
|
|
|
47
|
+
// Timeout tracking for cleanup
|
|
48
|
+
private openTimeout: ReturnType<typeof setTimeout> | null = null
|
|
49
|
+
private closeTimeout: ReturnType<typeof setTimeout> | null = null
|
|
50
|
+
|
|
45
51
|
// Events
|
|
46
52
|
public events = {
|
|
47
53
|
search: undefined,
|
|
@@ -87,7 +93,7 @@ export default class SlimSelect {
|
|
|
87
93
|
this.cssClasses = new CssClasses(config.cssClasses)
|
|
88
94
|
|
|
89
95
|
// Set events
|
|
90
|
-
const debounceEvents = ['
|
|
96
|
+
const debounceEvents = ['beforeOpen', 'afterOpen', 'beforeClose', 'afterClose']
|
|
91
97
|
for (const key in config.events) {
|
|
92
98
|
// Check if key exists in events
|
|
93
99
|
if (!config.events.hasOwnProperty(key)) {
|
|
@@ -132,16 +138,14 @@ export default class SlimSelect {
|
|
|
132
138
|
this.enable()
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
|
-
this.select.onOptionsChange = (data:
|
|
141
|
+
this.select.onOptionsChange = (data: (Option | Optgroup)[]) => {
|
|
136
142
|
// Run set data from the values given
|
|
137
143
|
this.setData(data)
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
// Set store class
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
config.data ? config.data : this.select.getData()
|
|
144
|
-
)
|
|
147
|
+
const data = config.data ? config.data : this.select.getData()
|
|
148
|
+
this.store = new Store(this.settings.isMultiple ? 'multiple' : 'single', data)
|
|
145
149
|
|
|
146
150
|
// If data is passed update the original select element
|
|
147
151
|
if (config.data) {
|
|
@@ -222,11 +226,11 @@ export default class SlimSelect {
|
|
|
222
226
|
this.render.disable()
|
|
223
227
|
}
|
|
224
228
|
|
|
225
|
-
public getData():
|
|
229
|
+
public getData(): Option[] | Optgroup[] {
|
|
226
230
|
return this.store.getData()
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
public setData(data:
|
|
233
|
+
public setData(data: (Partial<Option> | Partial<Optgroup>)[]): void {
|
|
230
234
|
// Get original selected values
|
|
231
235
|
const selected = this.store.getSelected()
|
|
232
236
|
|
|
@@ -309,7 +313,7 @@ export default class SlimSelect {
|
|
|
309
313
|
}
|
|
310
314
|
}
|
|
311
315
|
|
|
312
|
-
public addOption(option:
|
|
316
|
+
public addOption(option: Partial<Option>): void {
|
|
313
317
|
// Get original selected values
|
|
314
318
|
const selected = this.store.getSelected()
|
|
315
319
|
|
|
@@ -354,7 +358,7 @@ export default class SlimSelect {
|
|
|
354
358
|
|
|
355
359
|
this.settings.isOpen = true
|
|
356
360
|
// setTimeout is for animation completion
|
|
357
|
-
setTimeout(() => {
|
|
361
|
+
this.openTimeout = setTimeout(() => {
|
|
358
362
|
// Run afterOpen callback
|
|
359
363
|
if (this.events.afterOpen) {
|
|
360
364
|
this.events.afterOpen()
|
|
@@ -396,10 +400,8 @@ export default class SlimSelect {
|
|
|
396
400
|
// Tell render to close
|
|
397
401
|
this.render.close()
|
|
398
402
|
|
|
399
|
-
// Clear search
|
|
400
|
-
|
|
401
|
-
this.search('') // Clear search
|
|
402
|
-
}
|
|
403
|
+
// Clear search input visually (but don't trigger search event)
|
|
404
|
+
this.render.clearSearch()
|
|
403
405
|
|
|
404
406
|
// If we arent tabbing focus back on the main element
|
|
405
407
|
this.render.mainFocus(eventType)
|
|
@@ -409,7 +411,7 @@ export default class SlimSelect {
|
|
|
409
411
|
this.settings.isFullOpen = false
|
|
410
412
|
|
|
411
413
|
// Reset the content below
|
|
412
|
-
setTimeout(() => {
|
|
414
|
+
this.closeTimeout = setTimeout(() => {
|
|
413
415
|
// Run afterClose callback
|
|
414
416
|
if (this.events.afterClose) {
|
|
415
417
|
this.events.afterClose()
|
|
@@ -435,9 +437,8 @@ export default class SlimSelect {
|
|
|
435
437
|
// If no search event run regular search
|
|
436
438
|
if (!this.events.search) {
|
|
437
439
|
// If value is empty then render all options
|
|
438
|
-
this.
|
|
439
|
-
|
|
440
|
-
)
|
|
440
|
+
const searchResults = value === '' ? this.store.getData() : this.store.search(value, this.events.searchFilter!)
|
|
441
|
+
this.render.renderOptions(searchResults)
|
|
441
442
|
return
|
|
442
443
|
}
|
|
443
444
|
|
|
@@ -450,9 +451,15 @@ export default class SlimSelect {
|
|
|
450
451
|
// If the search event returns a promise
|
|
451
452
|
if (searchResp instanceof Promise) {
|
|
452
453
|
searchResp
|
|
453
|
-
.then((data:
|
|
454
|
-
// Update
|
|
455
|
-
this.
|
|
454
|
+
.then((data: (Partial<Option> | Partial<Optgroup>)[]) => {
|
|
455
|
+
// Update store data with search results, preserving selected options
|
|
456
|
+
this.store.setData(data, true)
|
|
457
|
+
|
|
458
|
+
// Update original select element
|
|
459
|
+
this.select.updateOptions(this.store.getData())
|
|
460
|
+
|
|
461
|
+
// Render the updated data
|
|
462
|
+
this.render.renderOptions(this.store.getData())
|
|
456
463
|
})
|
|
457
464
|
.catch((err: Error | string) => {
|
|
458
465
|
// Update the render with error
|
|
@@ -461,8 +468,14 @@ export default class SlimSelect {
|
|
|
461
468
|
|
|
462
469
|
return
|
|
463
470
|
} else if (Array.isArray(searchResp)) {
|
|
464
|
-
// Update
|
|
465
|
-
this.
|
|
471
|
+
// Update store data with search results, preserving selected options
|
|
472
|
+
this.store.setData(searchResp, true)
|
|
473
|
+
|
|
474
|
+
// Update original select element
|
|
475
|
+
this.select.updateOptions(this.store.getData())
|
|
476
|
+
|
|
477
|
+
// Render the updated data
|
|
478
|
+
this.render.renderOptions(this.store.getData())
|
|
466
479
|
} else {
|
|
467
480
|
// Update the render with error
|
|
468
481
|
this.render.renderError('Search event must return a promise or an array of data')
|
|
@@ -470,6 +483,20 @@ export default class SlimSelect {
|
|
|
470
483
|
}
|
|
471
484
|
|
|
472
485
|
public destroy(): void {
|
|
486
|
+
// Clear any pending timeouts
|
|
487
|
+
if (this.openTimeout) {
|
|
488
|
+
clearTimeout(this.openTimeout)
|
|
489
|
+
this.openTimeout = null
|
|
490
|
+
}
|
|
491
|
+
if (this.closeTimeout) {
|
|
492
|
+
clearTimeout(this.closeTimeout)
|
|
493
|
+
this.closeTimeout = null
|
|
494
|
+
}
|
|
495
|
+
if (this.settings.intervalMove) {
|
|
496
|
+
clearInterval(this.settings.intervalMove)
|
|
497
|
+
this.settings.intervalMove = null
|
|
498
|
+
}
|
|
499
|
+
|
|
473
500
|
// Remove all event listeners
|
|
474
501
|
document.removeEventListener('click', this.documentClick)
|
|
475
502
|
window.removeEventListener('resize', this.windowResize, false)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
|
|
2
|
+
import SlimSelectCore from '../index'
|
|
3
|
+
import type { Config, Option, Optgroup } from '../index'
|
|
4
|
+
|
|
5
|
+
export interface SlimSelectProps {
|
|
6
|
+
data?: (Partial<Option> | Partial<Optgroup>)[]
|
|
7
|
+
settings?: Config['settings']
|
|
8
|
+
events?: Config['events']
|
|
9
|
+
cssClasses?: Config['cssClasses']
|
|
10
|
+
value?: string | string[]
|
|
11
|
+
onChange?: (value: string | string[]) => void
|
|
12
|
+
children?: React.ReactNode
|
|
13
|
+
multiple?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SlimSelectRef {
|
|
17
|
+
slimSelect: SlimSelectCore | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SlimSelect = forwardRef<SlimSelectRef, SlimSelectProps>(
|
|
21
|
+
({ data, settings, events, cssClasses, value, onChange, children, multiple }, ref) => {
|
|
22
|
+
const selectRef = useRef<HTMLSelectElement>(null)
|
|
23
|
+
const slimSelectRef = useRef<SlimSelectCore | null>(null)
|
|
24
|
+
const isInitialMount = useRef(true)
|
|
25
|
+
|
|
26
|
+
// Expose SlimSelect instance to parent via ref
|
|
27
|
+
useImperativeHandle(ref, () => ({
|
|
28
|
+
slimSelect: slimSelectRef.current
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
// Initialize SlimSelect
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!selectRef.current) return
|
|
34
|
+
|
|
35
|
+
const config: Config = {
|
|
36
|
+
select: selectRef.current
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (data) {
|
|
40
|
+
config.data = data
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (settings) {
|
|
44
|
+
config.settings = settings
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (cssClasses) {
|
|
48
|
+
config.cssClasses = cssClasses
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Wrap onChange in afterChange event
|
|
52
|
+
const ogAfterChange = events?.afterChange
|
|
53
|
+
config.events = {
|
|
54
|
+
...events,
|
|
55
|
+
afterChange: (newVal) => {
|
|
56
|
+
const newValue = multiple ? newVal.map((option) => option.value) : (newVal[0]?.value ?? '')
|
|
57
|
+
|
|
58
|
+
if (onChange) {
|
|
59
|
+
onChange(newValue)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (ogAfterChange) {
|
|
63
|
+
ogAfterChange(newVal)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
slimSelectRef.current = new SlimSelectCore(config)
|
|
69
|
+
|
|
70
|
+
// Set initial value if provided
|
|
71
|
+
if (value !== undefined) {
|
|
72
|
+
slimSelectRef.current.setSelected(value, false)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
if (slimSelectRef.current) {
|
|
77
|
+
slimSelectRef.current.destroy()
|
|
78
|
+
slimSelectRef.current = null
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}, []) // Only run on mount/unmount
|
|
82
|
+
|
|
83
|
+
// Handle value changes from parent
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (isInitialMount.current) {
|
|
86
|
+
isInitialMount.current = false
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (slimSelectRef.current && value !== undefined) {
|
|
91
|
+
slimSelectRef.current.setSelected(value, false)
|
|
92
|
+
}
|
|
93
|
+
}, [value])
|
|
94
|
+
|
|
95
|
+
// Handle data changes
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (slimSelectRef.current && data && !isInitialMount.current) {
|
|
98
|
+
slimSelectRef.current.setData(data)
|
|
99
|
+
}
|
|
100
|
+
}, [data])
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<select ref={selectRef} multiple={multiple}>
|
|
104
|
+
{children}
|
|
105
|
+
</select>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
SlimSelect.displayName = 'SlimSelect'
|
|
111
|
+
|
|
112
|
+
export default SlimSelect
|