slim-select 3.4.2 → 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.
- package/dist/classes.d.ts +46 -0
- package/dist/helpers.d.ts +5 -0
- package/dist/index.d.ts +53 -318
- package/dist/index.umd.d.ts +2 -0
- package/dist/react/index.d.ts +4 -318
- package/dist/react/index.js +1459 -1696
- package/dist/react/index.umd.js +2 -2
- package/dist/react/react.d.ts +17 -0
- package/dist/react/react.test.d.ts +1 -0
- package/dist/react/slim-select/react/index.d.ts +4 -0
- package/dist/render.d.ts +88 -0
- package/dist/select.d.ts +38 -0
- package/dist/settings.d.ts +39 -0
- package/dist/slimselect.cjs.js +2 -2
- package/dist/slimselect.cjs.js.map +1 -1
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +1233 -1384
- package/dist/slimselect.es.js.map +1 -1
- package/dist/slimselect.iife.js +2 -2
- package/dist/slimselect.iife.js.map +1 -1
- package/dist/slimselect.js +2 -2
- package/dist/slimselect.js.map +1 -1
- package/dist/src/docs/main.d.ts +0 -0
- package/dist/src/docs/router.d.ts +2 -0
- package/dist/src/docs/store.d.ts +12 -0
- package/dist/store.d.ts +53 -0
- package/dist/vue/index.d.ts +4 -318
- package/dist/vue/index.js +1335 -1503
- package/dist/vue/index.umd.js +1 -1
- package/dist/vue/slim-select/vue/index.d.ts +4 -0
- package/package.json +30 -36
- package/src/slim-select/slimselect.scss +34 -5
- package/.nvmrc +0 -1
- package/.prettierignore +0 -1
- package/.prettierrc +0 -7
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -1
- package/cdn.html +0 -21
- package/docs.go +0 -12
- package/example.html +0 -25
- package/index.html +0 -76
- package/release.js +0 -191
- package/sitemap.js +0 -108
- package/src/slim-select/accessibility.test.ts +0 -782
- package/src/slim-select/classes.test.ts +0 -73
- package/src/slim-select/classes.ts +0 -117
- package/src/slim-select/helpers.test.ts +0 -162
- package/src/slim-select/helpers.ts +0 -61
- package/src/slim-select/index.test.ts +0 -1356
- package/src/slim-select/index.ts +0 -594
- package/src/slim-select/index.umd.ts +0 -8
- package/src/slim-select/react/index.tsx +0 -9
- package/src/slim-select/react/react.test.tsx +0 -498
- package/src/slim-select/react/react.tsx +0 -209
- package/src/slim-select/render.test.ts +0 -1877
- package/src/slim-select/render.ts +0 -1765
- package/src/slim-select/select.test.ts +0 -683
- package/src/slim-select/select.ts +0 -659
- package/src/slim-select/settings.test.ts +0 -285
- package/src/slim-select/settings.ts +0 -78
- package/src/slim-select/store.test.ts +0 -1007
- package/src/slim-select/store.ts +0 -433
- package/src/slim-select/vue/index.ts +0 -9
- package/src/slim-select/vue/vue.test.ts +0 -1091
- package/src/slim-select/vue/vue.vue +0 -224
- package/tsconfig.json +0 -26
- package/vite.config.docs.mts +0 -29
- package/vite.config.lib.es-cjs.ts +0 -40
- package/vite.config.lib.umd-iife.ts +0 -34
- package/vite.config.react.mts +0 -41
- package/vite.config.vue.mts +0 -40
- package/vitest.config.ts +0 -30
package/src/slim-select/index.ts
DELETED
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
import CssClasses from './classes'
|
|
2
|
-
import { debounce, hasClassInTree, isEqual } from './helpers'
|
|
3
|
-
import Render from './render'
|
|
4
|
-
import Select from './select'
|
|
5
|
-
import Settings from './settings'
|
|
6
|
-
import Store, { Option, Optgroup } from './store'
|
|
7
|
-
|
|
8
|
-
// Export classes
|
|
9
|
-
export { Settings, Option, Optgroup }
|
|
10
|
-
|
|
11
|
-
// Export interfaces from render
|
|
12
|
-
export type { Main, Content, Search } from './render'
|
|
13
|
-
|
|
14
|
-
export interface Config {
|
|
15
|
-
select: string | Element
|
|
16
|
-
data?: (Partial<Option> | Partial<Optgroup>)[]
|
|
17
|
-
settings?: Partial<Settings>
|
|
18
|
-
cssClasses?: Partial<CssClasses>
|
|
19
|
-
events?: Events
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface Events {
|
|
23
|
-
search?: (
|
|
24
|
-
searchValue: string,
|
|
25
|
-
currentData: (Option | Optgroup)[]
|
|
26
|
-
) => Promise<(Partial<Option> | Partial<Optgroup>)[]> | (Partial<Option> | Partial<Optgroup>)[]
|
|
27
|
-
searchFilter?: (option: Option, search: string) => boolean
|
|
28
|
-
addable?: (
|
|
29
|
-
value: string
|
|
30
|
-
) => Promise<Partial<Option> | string> | Partial<Option> | string | false | null | undefined | Error
|
|
31
|
-
beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void
|
|
32
|
-
afterChange?: (newVal: Option[]) => void
|
|
33
|
-
beforeOpen?: () => void
|
|
34
|
-
afterOpen?: () => void
|
|
35
|
-
beforeClose?: () => void
|
|
36
|
-
afterClose?: () => void
|
|
37
|
-
error?: (err: Error) => void
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default class SlimSelect {
|
|
41
|
-
public selectEl: HTMLSelectElement
|
|
42
|
-
|
|
43
|
-
// Classes
|
|
44
|
-
public settings!: Settings
|
|
45
|
-
public cssClasses!: CssClasses
|
|
46
|
-
public select!: Select
|
|
47
|
-
public store!: Store
|
|
48
|
-
public render!: Render
|
|
49
|
-
|
|
50
|
-
// Timeout tracking for cleanup
|
|
51
|
-
private openTimeout: ReturnType<typeof setTimeout> | null = null
|
|
52
|
-
private closeTimeout: ReturnType<typeof setTimeout> | null = null
|
|
53
|
-
|
|
54
|
-
// Events
|
|
55
|
-
public events = {
|
|
56
|
-
search: undefined,
|
|
57
|
-
searchFilter: (opt: Option, search: string) => {
|
|
58
|
-
return opt.text.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
|
59
|
-
},
|
|
60
|
-
addable: undefined,
|
|
61
|
-
beforeChange: undefined,
|
|
62
|
-
afterChange: undefined,
|
|
63
|
-
beforeOpen: undefined,
|
|
64
|
-
afterOpen: undefined,
|
|
65
|
-
beforeClose: undefined,
|
|
66
|
-
afterClose: undefined
|
|
67
|
-
} as Events
|
|
68
|
-
|
|
69
|
-
constructor(config: Config) {
|
|
70
|
-
// Make sure you get the right element
|
|
71
|
-
this.selectEl = (
|
|
72
|
-
typeof config.select === 'string' ? document.querySelector(config.select) : config.select
|
|
73
|
-
) as HTMLSelectElement
|
|
74
|
-
if (!this.selectEl) {
|
|
75
|
-
if (config.events && config.events.error) {
|
|
76
|
-
config.events.error(new Error('Could not find select element'))
|
|
77
|
-
}
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
if (this.selectEl.tagName !== 'SELECT') {
|
|
81
|
-
if (config.events && config.events.error) {
|
|
82
|
-
config.events.error(new Error('Element isnt of type select'))
|
|
83
|
-
}
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// If select already has a slim select id on it lets destroy it first
|
|
88
|
-
if (this.selectEl.dataset.ssid) {
|
|
89
|
-
this.destroy()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Set settings
|
|
93
|
-
this.settings = new Settings(config.settings)
|
|
94
|
-
|
|
95
|
-
// Set CSS classes
|
|
96
|
-
this.cssClasses = new CssClasses(config.cssClasses)
|
|
97
|
-
|
|
98
|
-
// Set events
|
|
99
|
-
const debounceEvents = ['beforeOpen', 'afterOpen', 'beforeClose', 'afterClose']
|
|
100
|
-
for (const key in config.events) {
|
|
101
|
-
// Check if key exists in events
|
|
102
|
-
if (!config.events.hasOwnProperty(key)) {
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check if key is in debounceEvents
|
|
107
|
-
if (debounceEvents.indexOf(key) !== -1) {
|
|
108
|
-
;(this.events as { [key: string]: any })[key] = debounce((config.events as { [key: string]: any })[key], 100)
|
|
109
|
-
} else {
|
|
110
|
-
;(this.events as { [key: string]: any })[key] = (config.events as { [key: string]: any })[key]
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Upate settings with type, style and classname
|
|
115
|
-
this.settings.disabled = config.settings?.disabled ? config.settings.disabled : this.selectEl.disabled
|
|
116
|
-
this.settings.isMultiple = this.selectEl.multiple
|
|
117
|
-
this.settings.style = this.selectEl.style.cssText
|
|
118
|
-
this.settings.class = this.selectEl.className.split(' ')
|
|
119
|
-
|
|
120
|
-
// Set select class
|
|
121
|
-
this.select = new Select(this.selectEl)
|
|
122
|
-
// Ensure the select has an id for label associations
|
|
123
|
-
if (!this.selectEl.id) {
|
|
124
|
-
this.selectEl.id = this.settings.id
|
|
125
|
-
}
|
|
126
|
-
this.select.updateSelect(this.settings.id, this.settings.style, this.settings.class)
|
|
127
|
-
this.select.hideUI() // Hide the original select element
|
|
128
|
-
|
|
129
|
-
// Add select listeners
|
|
130
|
-
this.select.onValueChange = (options: Option[]) => {
|
|
131
|
-
// Run set selected from the values given
|
|
132
|
-
this.setSelected(options.map((option) => option.id))
|
|
133
|
-
}
|
|
134
|
-
this.select.onClassChange = (classes: string[]) => {
|
|
135
|
-
// Update settings with new class
|
|
136
|
-
this.settings.class = classes
|
|
137
|
-
|
|
138
|
-
// Run render updateClassStyles
|
|
139
|
-
this.render.updateClassStyles()
|
|
140
|
-
}
|
|
141
|
-
this.select.onDisabledChange = (disabled: boolean) => {
|
|
142
|
-
if (disabled) {
|
|
143
|
-
this.disable()
|
|
144
|
-
} else {
|
|
145
|
-
this.enable()
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
this.select.onOptionsChange = (data: (Option | Optgroup)[]) => {
|
|
149
|
-
// Process the data (including empty data for clearing all options)
|
|
150
|
-
// Empty data is safe to process here because if we were updating, the change would be queued
|
|
151
|
-
// and onOptionsChange wouldn't be called directly
|
|
152
|
-
this.setData(data || [])
|
|
153
|
-
}
|
|
154
|
-
// Set up label click handler to toggle SlimSelect
|
|
155
|
-
// This allows clicking the label to open/close, matching the main div behavior
|
|
156
|
-
this.select.onLabelClick = () => {
|
|
157
|
-
if (!this.settings.disabled) {
|
|
158
|
-
this.settings.isOpen ? this.close() : this.open()
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Set store class
|
|
163
|
-
const data = config.data ? config.data : this.select.getData()
|
|
164
|
-
this.store = new Store(this.settings.isMultiple ? 'multiple' : 'single', data)
|
|
165
|
-
|
|
166
|
-
// If data is passed update the original select element
|
|
167
|
-
if (config.data) {
|
|
168
|
-
this.select.updateOptions(this.store.getData())
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Set render renderCallbacks
|
|
172
|
-
const renderCallbacks = {
|
|
173
|
-
open: this.open.bind(this),
|
|
174
|
-
close: this.close.bind(this),
|
|
175
|
-
addable: this.events.addable ? this.events.addable : undefined,
|
|
176
|
-
setSelected: this.setSelected.bind(this),
|
|
177
|
-
addOption: this.addOption.bind(this),
|
|
178
|
-
search: this.search.bind(this),
|
|
179
|
-
beforeChange: this.events.beforeChange,
|
|
180
|
-
afterChange: this.events.afterChange
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Setup render class
|
|
184
|
-
this.render = new Render(this.settings, this.cssClasses, this.store, renderCallbacks)
|
|
185
|
-
this.render.renderValues()
|
|
186
|
-
this.render.renderOptions(this.store.getData())
|
|
187
|
-
|
|
188
|
-
// Add aria-label or aria-labelledby if exists
|
|
189
|
-
const selectAriaLabel = this.selectEl.getAttribute('aria-label')
|
|
190
|
-
const selectAriaLabelledBy = this.selectEl.getAttribute('aria-labelledby')
|
|
191
|
-
|
|
192
|
-
if (selectAriaLabel) {
|
|
193
|
-
this.render.main.main.setAttribute('aria-label', selectAriaLabel)
|
|
194
|
-
} else if (selectAriaLabelledBy) {
|
|
195
|
-
this.render.main.main.setAttribute('aria-labelledby', selectAriaLabelledBy)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Add render after original select element
|
|
199
|
-
if (this.selectEl.parentNode) {
|
|
200
|
-
this.selectEl.parentNode.insertBefore(this.render.main.main, this.selectEl.nextSibling)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Add window resize listener to moveContent if window size changes
|
|
204
|
-
window.addEventListener('resize', this.windowResize, false)
|
|
205
|
-
|
|
206
|
-
// If the user wants to show the content forcibly on a specific side,
|
|
207
|
-
// there is no need to listen for scroll events
|
|
208
|
-
if (this.settings.openPosition === 'auto') {
|
|
209
|
-
window.addEventListener('scroll', this.windowScroll, false)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Add window visibility change listener to closeContent if window is hidden
|
|
213
|
-
document.addEventListener('visibilitychange', this.windowVisibilityChange)
|
|
214
|
-
|
|
215
|
-
// If disabled lets call it
|
|
216
|
-
if (this.settings.disabled) {
|
|
217
|
-
this.disable()
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// If alwaysOpnen then open it
|
|
221
|
-
if (this.settings.alwaysOpen) {
|
|
222
|
-
this.open()
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Set up label handlers to open SlimSelect when label is clicked
|
|
226
|
-
this.select.setupLabelHandlers()
|
|
227
|
-
|
|
228
|
-
// Add SlimSelect to select element
|
|
229
|
-
;(this.selectEl as any).slim = this
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Set to enabled and remove disabled classes
|
|
233
|
-
public enable(): void {
|
|
234
|
-
this.settings.disabled = false
|
|
235
|
-
|
|
236
|
-
this.select.enable()
|
|
237
|
-
this.render.enable()
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Set to disabled and add disabled classes
|
|
241
|
-
public disable(): void {
|
|
242
|
-
this.settings.disabled = true
|
|
243
|
-
|
|
244
|
-
this.select.disable()
|
|
245
|
-
this.render.disable()
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
public getData(): Option[] | Optgroup[] {
|
|
249
|
-
return this.store.getData()
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public setData(data: (Partial<Option> | Partial<Optgroup>)[]): void {
|
|
253
|
-
// Get original selected values
|
|
254
|
-
const selected = this.store.getSelected()
|
|
255
|
-
|
|
256
|
-
// Validate data
|
|
257
|
-
const err = this.store.validateDataArray(data)
|
|
258
|
-
if (err) {
|
|
259
|
-
if (this.events.error) {
|
|
260
|
-
this.events.error(err)
|
|
261
|
-
}
|
|
262
|
-
return
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Update the store
|
|
266
|
-
this.store.setData(data)
|
|
267
|
-
const dataClean = this.store.getData()
|
|
268
|
-
|
|
269
|
-
// Update original select element
|
|
270
|
-
this.select.updateOptions(dataClean)
|
|
271
|
-
|
|
272
|
-
// Update the render
|
|
273
|
-
this.render.renderValues()
|
|
274
|
-
this.render.renderOptions(dataClean)
|
|
275
|
-
|
|
276
|
-
// Trigger afterChange event, if it doesnt equal the original selected values
|
|
277
|
-
if (this.events.afterChange && !isEqual(selected, this.store.getSelected())) {
|
|
278
|
-
this.events.afterChange(this.store.getSelectedOptions())
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
public getSelected(): string[] {
|
|
283
|
-
let options = this.store.getSelectedOptions()
|
|
284
|
-
if (this.settings.keepOrder) {
|
|
285
|
-
options = this.store.selectedOrderOptions(options)
|
|
286
|
-
}
|
|
287
|
-
return options.map((option) => option.value)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Will take in a string or array of strings and set the selected by either the id or value
|
|
291
|
-
public setSelected(values: string | string[], runAfterChange = true): void {
|
|
292
|
-
// Get original selected values
|
|
293
|
-
const selected = this.store.getSelected()
|
|
294
|
-
const options = this.store.getDataOptions()
|
|
295
|
-
values = Array.isArray(values) ? values : [values]
|
|
296
|
-
const ids = []
|
|
297
|
-
|
|
298
|
-
// for back-compatibility support both, set by id and set by value
|
|
299
|
-
for (const value of values) {
|
|
300
|
-
if (options.find((option) => option.id == value)) {
|
|
301
|
-
ids.push(value)
|
|
302
|
-
continue
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// if option with given id is not found try to search by value
|
|
306
|
-
for (const option of options.filter((option) => option.value == value)) {
|
|
307
|
-
ids.push(option.id)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Update the store
|
|
312
|
-
this.store.setSelectedBy('id', ids)
|
|
313
|
-
const data = this.store.getData()
|
|
314
|
-
|
|
315
|
-
// Update the select element
|
|
316
|
-
this.select.updateOptions(data)
|
|
317
|
-
|
|
318
|
-
// Update the render
|
|
319
|
-
this.render.renderValues()
|
|
320
|
-
|
|
321
|
-
// If there is a search input value lets run through the search again
|
|
322
|
-
// Otherwise we will just render the options from store data
|
|
323
|
-
if (this.render.content.search.input.value !== '') {
|
|
324
|
-
this.search(this.render.content.search.input.value)
|
|
325
|
-
} else {
|
|
326
|
-
this.render.renderOptions(data)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Trigger afterChange event, if it doesnt equal the original selected values
|
|
330
|
-
if (runAfterChange && this.events.afterChange && !isEqual(selected, this.store.getSelected())) {
|
|
331
|
-
this.events.afterChange(this.store.getSelectedOptions())
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
public addOption(option: Partial<Option>): void {
|
|
336
|
-
// Get original selected values
|
|
337
|
-
const selected = this.store.getSelected()
|
|
338
|
-
|
|
339
|
-
// Add option to store if it does not already include the option
|
|
340
|
-
if (!this.store.getDataOptions().some((o) => o.value === (option.value ?? option.text))) {
|
|
341
|
-
this.store.addOption(option)
|
|
342
|
-
}
|
|
343
|
-
const data = this.store.getData()
|
|
344
|
-
|
|
345
|
-
// Update the select element
|
|
346
|
-
this.select.updateOptions(data)
|
|
347
|
-
|
|
348
|
-
// Update the render
|
|
349
|
-
this.render.renderValues()
|
|
350
|
-
this.render.renderOptions(data)
|
|
351
|
-
|
|
352
|
-
// Trigger afterChange event, if it doesnt equal the original selected values
|
|
353
|
-
if (this.events.afterChange && !isEqual(selected, this.store.getSelected())) {
|
|
354
|
-
this.events.afterChange(this.store.getSelectedOptions())
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
public open(): void {
|
|
359
|
-
// Dont open if disabled
|
|
360
|
-
// Dont do anything if the content is already open
|
|
361
|
-
if (this.settings.disabled || this.settings.isOpen) {
|
|
362
|
-
return
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Clear any pending close timeout to prevent race conditions
|
|
366
|
-
if (this.closeTimeout) {
|
|
367
|
-
clearTimeout(this.closeTimeout)
|
|
368
|
-
this.closeTimeout = null
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Run beforeOpen callback
|
|
372
|
-
if (this.events.beforeOpen) {
|
|
373
|
-
this.events.beforeOpen()
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Tell render to open
|
|
377
|
-
this.render.open()
|
|
378
|
-
|
|
379
|
-
// Focus on input field only if search is enabled
|
|
380
|
-
if (this.settings.showSearch && this.settings.focusSearch) {
|
|
381
|
-
this.render.searchFocus()
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
this.settings.isOpen = true
|
|
385
|
-
// setTimeout is for animation completion
|
|
386
|
-
this.openTimeout = setTimeout(() => {
|
|
387
|
-
// Run afterOpen callback
|
|
388
|
-
if (this.events.afterOpen) {
|
|
389
|
-
this.events.afterOpen()
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Update settings
|
|
393
|
-
// Prevent overide if user close fast without wait full open
|
|
394
|
-
// For detail see issue https://github.com/brianvoe/slim-select/issues/397
|
|
395
|
-
if (this.settings.isOpen) {
|
|
396
|
-
this.settings.isFullOpen = true
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Add onclick listener to document to closeContent if clicked outside
|
|
400
|
-
document.addEventListener('click', this.documentClick)
|
|
401
|
-
}, this.settings.timeoutDelay)
|
|
402
|
-
|
|
403
|
-
// Start an interval to check if main has moved
|
|
404
|
-
// in order to keep content close to main
|
|
405
|
-
if (this.settings.contentPosition === 'absolute') {
|
|
406
|
-
if (this.settings.intervalMove) {
|
|
407
|
-
clearInterval(this.settings.intervalMove)
|
|
408
|
-
}
|
|
409
|
-
this.settings.intervalMove = setInterval(this.render.moveContent.bind(this.render), 500)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
public close(eventType: string | null = null): void {
|
|
414
|
-
// Dont do anything if the content is already closed
|
|
415
|
-
// Dont do anything if alwaysOpen is true
|
|
416
|
-
if (!this.settings.isOpen || this.settings.alwaysOpen) {
|
|
417
|
-
return
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Clear any pending open timeout to prevent race conditions
|
|
421
|
-
if (this.openTimeout) {
|
|
422
|
-
clearTimeout(this.openTimeout)
|
|
423
|
-
this.openTimeout = null
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Run beforeClose calback
|
|
427
|
-
if (this.events.beforeClose) {
|
|
428
|
-
this.events.beforeClose()
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Tell render to close
|
|
432
|
-
this.render.close()
|
|
433
|
-
|
|
434
|
-
// Clear search only if not empty and keepSearch is false
|
|
435
|
-
if (!this.settings.keepSearch && this.render.content.search.input.value !== '') {
|
|
436
|
-
this.search('') // Clear search
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// If we arent tabbing focus back on the main element
|
|
440
|
-
this.render.mainFocus(eventType)
|
|
441
|
-
|
|
442
|
-
// Update settings
|
|
443
|
-
this.settings.isOpen = false
|
|
444
|
-
this.settings.isFullOpen = false
|
|
445
|
-
|
|
446
|
-
// Reset the content below
|
|
447
|
-
this.closeTimeout = setTimeout(() => {
|
|
448
|
-
// Run afterClose callback
|
|
449
|
-
if (this.events.afterClose) {
|
|
450
|
-
this.events.afterClose()
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Add onclick listener to document to closeContent if clicked outside
|
|
454
|
-
document.removeEventListener('click', this.documentClick)
|
|
455
|
-
}, this.settings.timeoutDelay)
|
|
456
|
-
|
|
457
|
-
if (this.settings.intervalMove) {
|
|
458
|
-
clearInterval(this.settings.intervalMove)
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Take in string value and search current options
|
|
463
|
-
public search(value: string): void {
|
|
464
|
-
// If the passed in value is not the same as the search input value
|
|
465
|
-
// then lets update the search input value
|
|
466
|
-
if (this.render.content.search.input.value !== value) {
|
|
467
|
-
this.render.content.search.input.value = value
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// If value is empty then render all options
|
|
471
|
-
if (value === '') {
|
|
472
|
-
this.render.renderOptions(this.store.getData())
|
|
473
|
-
return
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// If no search event run regular search
|
|
477
|
-
if (!this.events.search) {
|
|
478
|
-
// If value is empty then render all options
|
|
479
|
-
const searchResults = value === '' ? this.store.getData() : this.store.search(value, this.events.searchFilter!)
|
|
480
|
-
this.render.renderOptions(searchResults)
|
|
481
|
-
return
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Search event exists so lets render the searching text
|
|
485
|
-
this.render.renderSearching()
|
|
486
|
-
|
|
487
|
-
// Based upon the search event deal with the response
|
|
488
|
-
const searchResp = this.events.search(value, this.store.getSelectedOptions())
|
|
489
|
-
|
|
490
|
-
// If the search event returns a promise
|
|
491
|
-
if (searchResp instanceof Promise) {
|
|
492
|
-
searchResp
|
|
493
|
-
.then((data: (Partial<Option> | Partial<Optgroup>)[]) => {
|
|
494
|
-
// Update store data with search results, preserving selected options
|
|
495
|
-
this.store.setData(data, true)
|
|
496
|
-
|
|
497
|
-
// Update original select element
|
|
498
|
-
this.select.updateOptions(this.store.getData())
|
|
499
|
-
|
|
500
|
-
// Render the updated data
|
|
501
|
-
this.render.renderOptions(this.store.getData())
|
|
502
|
-
})
|
|
503
|
-
.catch((err: Error | string) => {
|
|
504
|
-
// Update the render with error
|
|
505
|
-
this.render.renderError(typeof err === 'string' ? err : err.message)
|
|
506
|
-
})
|
|
507
|
-
|
|
508
|
-
return
|
|
509
|
-
} else if (Array.isArray(searchResp)) {
|
|
510
|
-
// Update store data with search results, preserving selected options
|
|
511
|
-
this.store.setData(searchResp, true)
|
|
512
|
-
|
|
513
|
-
// Update original select element
|
|
514
|
-
this.select.updateOptions(this.store.getData())
|
|
515
|
-
|
|
516
|
-
// Render the updated data
|
|
517
|
-
this.render.renderOptions(this.store.getData())
|
|
518
|
-
} else {
|
|
519
|
-
// Update the render with error
|
|
520
|
-
this.render.renderError('Search event must return a promise or an array of data')
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
public destroy(): void {
|
|
525
|
-
// Clear any pending timeouts
|
|
526
|
-
if (this.openTimeout) {
|
|
527
|
-
clearTimeout(this.openTimeout)
|
|
528
|
-
this.openTimeout = null
|
|
529
|
-
}
|
|
530
|
-
if (this.closeTimeout) {
|
|
531
|
-
clearTimeout(this.closeTimeout)
|
|
532
|
-
this.closeTimeout = null
|
|
533
|
-
}
|
|
534
|
-
if (this.settings.intervalMove) {
|
|
535
|
-
clearInterval(this.settings.intervalMove)
|
|
536
|
-
this.settings.intervalMove = null
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// Remove all event listeners
|
|
540
|
-
document.removeEventListener('click', this.documentClick)
|
|
541
|
-
window.removeEventListener('resize', this.windowResize, false)
|
|
542
|
-
if (this.settings.openPosition === 'auto') {
|
|
543
|
-
window.removeEventListener('scroll', this.windowScroll, false)
|
|
544
|
-
}
|
|
545
|
-
document.removeEventListener('visibilitychange', this.windowVisibilityChange)
|
|
546
|
-
|
|
547
|
-
// Delete the store data
|
|
548
|
-
this.store.setData([])
|
|
549
|
-
|
|
550
|
-
// Remove the render
|
|
551
|
-
this.render.destroy()
|
|
552
|
-
|
|
553
|
-
// Show the original select element
|
|
554
|
-
this.select.destroy()
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
private windowResize: (e: Event) => void = debounce(() => {
|
|
558
|
-
if (!this.settings.isOpen && !this.settings.isFullOpen) {
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
this.render.moveContent()
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
// Event listener for window scrolling
|
|
566
|
-
private windowScroll: (e: Event) => void = debounce(() => {
|
|
567
|
-
// If the content is not open, there is no need to move it
|
|
568
|
-
if (!this.settings.isOpen && !this.settings.isFullOpen) {
|
|
569
|
-
return
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
this.render.moveContent()
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
// Event listener for document click
|
|
576
|
-
private documentClick: (e: Event) => void = (e: Event) => {
|
|
577
|
-
// If the content is not open, there is no need to close it
|
|
578
|
-
if (!this.settings.isOpen) {
|
|
579
|
-
return
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Check if the click was on the content by looking at the parents
|
|
583
|
-
if (e.target && !hasClassInTree(e.target as HTMLElement, this.settings.id)) {
|
|
584
|
-
this.close(e.type)
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Event Listener for window visibility change
|
|
589
|
-
private windowVisibilityChange: (e: Event) => void = () => {
|
|
590
|
-
if (document.hidden) {
|
|
591
|
-
this.close()
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// UMD entry point - only exports the default SlimSelect class
|
|
2
|
-
// This avoids the mixed exports issue for UMD/IIFE builds
|
|
3
|
-
import SlimSelect, * as namedExports from './index'
|
|
4
|
-
|
|
5
|
-
// Attach all named exports as properties on the default export
|
|
6
|
-
Object.assign(SlimSelect, namedExports)
|
|
7
|
-
|
|
8
|
-
export default SlimSelect
|