slim-select 2.3.3 → 2.4.1
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/helpers.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/render.d.ts +3 -2
- package/dist/settings.d.ts +1 -2
- package/dist/slimselect.cjs.js +44 -39
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +44 -39
- package/dist/slimselect.global.js +44 -39
- package/dist/slimselect.js +44 -39
- package/dist/slimselect.min.js +1 -1
- package/dist/slimselect.umd.js +44 -39
- package/dist/slimselect.umd.min.js +1 -1
- package/package.json +14 -13
- package/src/slim-select/helpers.ts +12 -2
- package/src/slim-select/index.ts +25 -15
- package/src/slim-select/render.ts +29 -29
- package/src/slim-select/settings.ts +1 -2
- package/src/slim-select/slimselect.scss +1 -0
- package/src/vue/dist/slim-select/helpers.d.ts +1 -1
- package/src/vue/dist/slim-select/index.d.ts +1 -1
- package/src/vue/dist/slim-select/render.d.ts +3 -2
- package/src/vue/dist/slim-select/settings.d.ts +1 -2
- package/src/vue/dist/slimselectvue.es.js +44 -39
- package/src/vue/dist/slimselectvue.global.js +44 -39
- package/src/vue/dist/slimselectvue.ssr.js +44 -39
- package/src/vue/dist/vue/slimselect.vue.d.ts +6 -7
- package/src/vue/package.json +2 -1
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slim-select",
|
|
3
3
|
"description": "Slim advanced select dropdown",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.1",
|
|
5
5
|
"author": "Brian Voelker <brian@webiswhatido.com> (http://webiswhatido.com)",
|
|
6
6
|
"homepage": "https://slimselectjs.com",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"bugs": {
|
|
9
9
|
"url": "https://github.com/brianvoe/slim-select/issues"
|
|
10
10
|
},
|
|
11
|
+
"type": "module",
|
|
11
12
|
"main": "dist/slimselect.ssr.js",
|
|
12
13
|
"browser": "dist/slimselect.umd.js",
|
|
13
14
|
"module": "dist/slimselect.es.js",
|
|
@@ -44,31 +45,31 @@
|
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@jest/globals": "^29.3.1",
|
|
46
47
|
"@rollup/plugin-babel": "^6.0.3",
|
|
47
|
-
"@rollup/plugin-commonjs": "^
|
|
48
|
+
"@rollup/plugin-commonjs": "^24.0.0",
|
|
48
49
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
|
49
|
-
"@rollup/plugin-terser": "^0.
|
|
50
|
-
"@rollup/plugin-typescript": "^
|
|
50
|
+
"@rollup/plugin-terser": "^0.3.0",
|
|
51
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
51
52
|
"@slim-select/vue": "workspace:src/vue",
|
|
52
53
|
"@types/downloadjs": "^1.4.3",
|
|
53
|
-
"@vitejs/plugin-vue": "^
|
|
54
|
+
"@vitejs/plugin-vue": "^4.0.0",
|
|
54
55
|
"clipboard": "^2.0.11",
|
|
55
56
|
"downloadjs": "^1.4.7",
|
|
56
57
|
"jest": "^29.3.1",
|
|
57
58
|
"jest-environment-jsdom": "^29.3.1",
|
|
58
|
-
"prettier": "^2.8.
|
|
59
|
+
"prettier": "^2.8.2",
|
|
59
60
|
"prismjs": "^1.29.0",
|
|
60
|
-
"rimraf": "^
|
|
61
|
-
"rollup": "^
|
|
61
|
+
"rimraf": "^4.0.4",
|
|
62
|
+
"rollup": "^3.10.0",
|
|
62
63
|
"rollup-plugin-typescript2": "^0.34.1",
|
|
63
64
|
"rollup-plugin-vue": "^6.0.0",
|
|
64
|
-
"sass": "^1.
|
|
65
|
-
"ts-jest": "^29.0.
|
|
65
|
+
"sass": "^1.57.1",
|
|
66
|
+
"ts-jest": "^29.0.5",
|
|
66
67
|
"tslib": "^2.4.1",
|
|
67
|
-
"typescript": "^4.9.
|
|
68
|
-
"vite": "^
|
|
68
|
+
"typescript": "^4.9.4",
|
|
69
|
+
"vite": "^4.0.4",
|
|
69
70
|
"vue": "^3.2.45",
|
|
70
71
|
"vue-router": "^4.1.6",
|
|
71
|
-
"vue-tsc": "^1.0.
|
|
72
|
+
"vue-tsc": "^1.0.24",
|
|
72
73
|
"vuex": "^4.0.2"
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -31,9 +31,10 @@ export function hasClassInTree(element: HTMLElement, className: string) {
|
|
|
31
31
|
return hasClass(element, className) || parentByClass(element, className)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// debounce will call the last requested function after the wait time
|
|
35
|
+
export function debounce<T extends (...args: any[]) => void>(func: T, wait = 50, immediate = false): () => void {
|
|
35
36
|
let timeout: any
|
|
36
|
-
return function (this: any, ...args: any[]) {
|
|
37
|
+
return function (this: any, ...args: any[]): void {
|
|
37
38
|
const context = self
|
|
38
39
|
const later = () => {
|
|
39
40
|
timeout = null
|
|
@@ -50,6 +51,15 @@ export function debounce(func: (...params: any[]) => void, wait = 50, immediate
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// reverseDebounce will call the function on the first call and then debounce
|
|
55
|
+
function reverseDebounce<T extends (...args: any[]) => void>(func: T, timeout: number): T {
|
|
56
|
+
let timer: NodeJS.Timeout | null = null
|
|
57
|
+
return function (...args: any[]): void {
|
|
58
|
+
if (!timer) func(...args)
|
|
59
|
+
timer = setTimeout(() => (timer = null), timeout)
|
|
60
|
+
} as T
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
export function isEqual(a: any, b: any) {
|
|
54
64
|
return JSON.stringify(a) === JSON.stringify(b)
|
|
55
65
|
}
|
package/src/slim-select/index.ts
CHANGED
|
@@ -153,6 +153,16 @@ export default class SlimSelect {
|
|
|
153
153
|
this.render.renderValues()
|
|
154
154
|
this.render.renderOptions(this.store.getData())
|
|
155
155
|
|
|
156
|
+
// Add aria-label or aria-labelledby if exists
|
|
157
|
+
const selectAriaLabel = this.selectEl.getAttribute('aria-label')
|
|
158
|
+
const selectAriaLabelledBy = this.selectEl.getAttribute('aria-labelledby')
|
|
159
|
+
|
|
160
|
+
if (selectAriaLabel) {
|
|
161
|
+
this.render.main.main.setAttribute('aria-label', selectAriaLabel)
|
|
162
|
+
} else if (selectAriaLabelledBy) {
|
|
163
|
+
this.render.main.main.setAttribute('aria-labelledby', selectAriaLabelledBy)
|
|
164
|
+
}
|
|
165
|
+
|
|
156
166
|
// Add render after original select element
|
|
157
167
|
if (this.selectEl.parentNode) {
|
|
158
168
|
this.selectEl.parentNode.insertBefore(this.render.main.main, this.selectEl.nextSibling)
|
|
@@ -309,9 +319,10 @@ export default class SlimSelect {
|
|
|
309
319
|
|
|
310
320
|
// Focus on input field only if search is enabled
|
|
311
321
|
if (this.settings.showSearch) {
|
|
312
|
-
this.render.searchFocus(
|
|
322
|
+
this.render.searchFocus()
|
|
313
323
|
}
|
|
314
324
|
|
|
325
|
+
this.settings.isOpen = true
|
|
315
326
|
// setTimeout is for animation completion
|
|
316
327
|
setTimeout(() => {
|
|
317
328
|
// Run afterOpen callback
|
|
@@ -320,7 +331,11 @@ export default class SlimSelect {
|
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
// Update settings
|
|
323
|
-
|
|
334
|
+
// Prevent overide if user close fast without wait full open
|
|
335
|
+
// For detail see issue https://github.com/brianvoe/slim-select/issues/397
|
|
336
|
+
if (this.settings.isOpen) {
|
|
337
|
+
this.settings.isFullOpen = true
|
|
338
|
+
}
|
|
324
339
|
}, this.settings.timeoutDelay)
|
|
325
340
|
|
|
326
341
|
// Start an interval to check if main has moved
|
|
@@ -333,7 +348,7 @@ export default class SlimSelect {
|
|
|
333
348
|
}
|
|
334
349
|
}
|
|
335
350
|
|
|
336
|
-
public close(): void {
|
|
351
|
+
public close(eventType: string | null = null): void {
|
|
337
352
|
// Dont do anything if the content is already closed
|
|
338
353
|
// Dont do anything if alwaysOpen is true
|
|
339
354
|
if (!this.settings.isOpen || this.settings.alwaysOpen) {
|
|
@@ -354,17 +369,17 @@ export default class SlimSelect {
|
|
|
354
369
|
}
|
|
355
370
|
|
|
356
371
|
// If we arent tabbing focus back on the main element
|
|
357
|
-
this.render.mainFocus(
|
|
372
|
+
this.render.mainFocus(eventType)
|
|
358
373
|
|
|
374
|
+
// Update settings
|
|
375
|
+
this.settings.isOpen = false
|
|
376
|
+
this.settings.isFullOpen = false
|
|
359
377
|
// Reset the content below
|
|
360
378
|
setTimeout(() => {
|
|
361
379
|
// Run afterClose callback
|
|
362
380
|
if (this.events.afterClose) {
|
|
363
381
|
this.events.afterClose()
|
|
364
382
|
}
|
|
365
|
-
|
|
366
|
-
// Update settings
|
|
367
|
-
this.settings.isOpen = false
|
|
368
383
|
}, this.settings.timeoutDelay)
|
|
369
384
|
|
|
370
385
|
if (this.settings.intervalMove) {
|
|
@@ -437,7 +452,7 @@ export default class SlimSelect {
|
|
|
437
452
|
}
|
|
438
453
|
|
|
439
454
|
private windowResize: (e: Event) => void = debounce(() => {
|
|
440
|
-
if (!this.settings.isOpen) {
|
|
455
|
+
if (!this.settings.isOpen && !this.settings.isFullOpen) {
|
|
441
456
|
return
|
|
442
457
|
}
|
|
443
458
|
|
|
@@ -447,7 +462,7 @@ export default class SlimSelect {
|
|
|
447
462
|
// Event listener for window scrolling
|
|
448
463
|
private windowScroll: (e: Event) => void = debounce(() => {
|
|
449
464
|
// If the content is not open, there is no need to move it
|
|
450
|
-
if (!this.settings.isOpen) {
|
|
465
|
+
if (!this.settings.isOpen && !this.settings.isFullOpen) {
|
|
451
466
|
return
|
|
452
467
|
}
|
|
453
468
|
|
|
@@ -463,19 +478,14 @@ export default class SlimSelect {
|
|
|
463
478
|
|
|
464
479
|
// Check if the click was on the content by looking at the parents
|
|
465
480
|
if (e.target && !hasClassInTree(e.target as HTMLElement, this.settings.id)) {
|
|
466
|
-
this.close()
|
|
481
|
+
this.close(e.type)
|
|
467
482
|
}
|
|
468
483
|
}
|
|
469
484
|
|
|
470
485
|
// Event Listener for window visibility change
|
|
471
486
|
private windowVisibilityChange: (e: Event) => void = () => {
|
|
472
487
|
if (document.hidden) {
|
|
473
|
-
this.settings.isWindowFocused = false
|
|
474
488
|
this.close()
|
|
475
|
-
} else {
|
|
476
|
-
setTimeout(() => {
|
|
477
|
-
this.settings.isWindowFocused = true
|
|
478
|
-
}, 20)
|
|
479
489
|
}
|
|
480
490
|
}
|
|
481
491
|
}
|
|
@@ -127,6 +127,7 @@ export default class Render {
|
|
|
127
127
|
|
|
128
128
|
// Add classes and styles to main/content
|
|
129
129
|
this.updateClassStyles()
|
|
130
|
+
this.updateAriaAttributes()
|
|
130
131
|
|
|
131
132
|
// Add content to the content location settings
|
|
132
133
|
this.settings.contentLocation.appendChild(this.content.main)
|
|
@@ -155,6 +156,7 @@ export default class Render {
|
|
|
155
156
|
|
|
156
157
|
// Add class to main container
|
|
157
158
|
this.main.main.classList.add(this.settings.openPosition === 'up' ? this.classes.openAbove : this.classes.openBelow)
|
|
159
|
+
this.main.main.setAttribute('aria-expanded', 'true')
|
|
158
160
|
|
|
159
161
|
// move the content in to the right location
|
|
160
162
|
this.moveContent()
|
|
@@ -173,6 +175,7 @@ export default class Render {
|
|
|
173
175
|
public close(): void {
|
|
174
176
|
this.main.main.classList.remove(this.classes.openAbove)
|
|
175
177
|
this.main.main.classList.remove(this.classes.openBelow)
|
|
178
|
+
this.main.main.setAttribute('aria-expanded', 'false')
|
|
176
179
|
this.content.main.classList.remove(this.classes.openAbove)
|
|
177
180
|
this.content.main.classList.remove(this.classes.openBelow)
|
|
178
181
|
this.main.arrow.path.setAttribute('d', this.classes.arrowClose)
|
|
@@ -211,23 +214,26 @@ export default class Render {
|
|
|
211
214
|
}
|
|
212
215
|
}
|
|
213
216
|
|
|
217
|
+
public updateAriaAttributes() {
|
|
218
|
+
this.main.main.role = 'combobox'
|
|
219
|
+
this.main.main.setAttribute('aria-haspopup', 'listbox')
|
|
220
|
+
this.main.main.setAttribute('aria-controls', this.content.main.id)
|
|
221
|
+
this.main.main.setAttribute('aria-expanded', 'false')
|
|
222
|
+
this.content.main.setAttribute('role', 'listbox')
|
|
223
|
+
// do an aria-labelledby here maybe
|
|
224
|
+
}
|
|
225
|
+
|
|
214
226
|
public mainDiv(): Main {
|
|
215
227
|
// Create main container
|
|
216
228
|
const main = document.createElement('div')
|
|
217
229
|
|
|
218
230
|
// Add id to data-id
|
|
219
231
|
main.dataset.id = this.settings.id
|
|
232
|
+
main.id = this.settings.id
|
|
220
233
|
|
|
221
234
|
// Set tabable to allow tabbing to the element
|
|
222
235
|
main.tabIndex = 0
|
|
223
236
|
|
|
224
|
-
// If main gets focus, open the content
|
|
225
|
-
main.onfocus = () => {
|
|
226
|
-
if (this.settings.triggerFocus && this.settings.isWindowFocused) {
|
|
227
|
-
this.callbacks.open()
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
237
|
// Deal with keyboard events on the main div
|
|
232
238
|
// This is to allow for normal selecting
|
|
233
239
|
// when you may not have a search bar
|
|
@@ -243,6 +249,8 @@ export default class Render {
|
|
|
243
249
|
this.callbacks.close()
|
|
244
250
|
return true // Continue doing normal tabbing
|
|
245
251
|
case 'Enter':
|
|
252
|
+
case ' ':
|
|
253
|
+
this.callbacks.open()
|
|
246
254
|
const highlighted = this.content.list.querySelector('.' + this.classes.highlighted) as HTMLDivElement
|
|
247
255
|
if (highlighted) {
|
|
248
256
|
highlighted.click()
|
|
@@ -346,13 +354,14 @@ export default class Render {
|
|
|
346
354
|
}
|
|
347
355
|
}
|
|
348
356
|
|
|
349
|
-
public mainFocus(
|
|
350
|
-
if (!trigger) {
|
|
351
|
-
this.settings.triggerFocus = false
|
|
352
|
-
}
|
|
357
|
+
public mainFocus(eventType: string | null): void {
|
|
353
358
|
// Trigger focus but dont scroll to it
|
|
354
|
-
|
|
355
|
-
this
|
|
359
|
+
// Need for prevent refocus the element if event is not keyboard event.
|
|
360
|
+
// For example if event is mouse click or tachpad click this condition prevent refocus on element
|
|
361
|
+
// because click by mouse change focus position and not need return focus to element.
|
|
362
|
+
if (eventType !== 'click') {
|
|
363
|
+
this.main.main.focus({ preventScroll: true })
|
|
364
|
+
}
|
|
356
365
|
}
|
|
357
366
|
|
|
358
367
|
public placeholder(): HTMLDivElement {
|
|
@@ -595,6 +604,7 @@ export default class Render {
|
|
|
595
604
|
|
|
596
605
|
// Add id to data-id
|
|
597
606
|
main.dataset.id = this.settings.id
|
|
607
|
+
main.id = this.settings.id
|
|
598
608
|
|
|
599
609
|
// Add search
|
|
600
610
|
const search = this.searchDiv()
|
|
@@ -671,7 +681,6 @@ export default class Render {
|
|
|
671
681
|
switch (e.key) {
|
|
672
682
|
case 'ArrowUp':
|
|
673
683
|
case 'ArrowDown':
|
|
674
|
-
this.callbacks.open()
|
|
675
684
|
e.key === 'ArrowDown' ? this.highlight('down') : this.highlight('up')
|
|
676
685
|
return false
|
|
677
686
|
case 'Tab':
|
|
@@ -685,6 +694,7 @@ export default class Render {
|
|
|
685
694
|
this.callbacks.close()
|
|
686
695
|
return false
|
|
687
696
|
case 'Enter':
|
|
697
|
+
case ' ':
|
|
688
698
|
if (this.callbacks.addable && e.ctrlKey) {
|
|
689
699
|
addable.click()
|
|
690
700
|
} else {
|
|
@@ -697,15 +707,6 @@ export default class Render {
|
|
|
697
707
|
}
|
|
698
708
|
}
|
|
699
709
|
|
|
700
|
-
// If focus is on the search input, open the dropdown
|
|
701
|
-
input.onfocus = () => {
|
|
702
|
-
// If we are already open, do nothing
|
|
703
|
-
if (this.settings.isOpen) {
|
|
704
|
-
return
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
this.callbacks.open()
|
|
708
|
-
}
|
|
709
710
|
main.appendChild(input)
|
|
710
711
|
|
|
711
712
|
// If addable is enabled, add the addable div
|
|
@@ -807,12 +808,8 @@ export default class Render {
|
|
|
807
808
|
return searchReturn
|
|
808
809
|
}
|
|
809
810
|
|
|
810
|
-
public searchFocus(
|
|
811
|
-
if (!trigger) {
|
|
812
|
-
this.settings.triggerFocus = false
|
|
813
|
-
}
|
|
811
|
+
public searchFocus(): void {
|
|
814
812
|
this.content.search.input.focus()
|
|
815
|
-
this.settings.triggerFocus = true
|
|
816
813
|
}
|
|
817
814
|
|
|
818
815
|
public getOptions(notPlaceholder = false, notDisabled = false, notHidden = false): HTMLDivElement[] {
|
|
@@ -897,7 +894,6 @@ export default class Render {
|
|
|
897
894
|
public listDiv(): HTMLDivElement {
|
|
898
895
|
const options = document.createElement('div')
|
|
899
896
|
options.classList.add(this.classes.list)
|
|
900
|
-
options.setAttribute('role', 'listbox')
|
|
901
897
|
|
|
902
898
|
return options
|
|
903
899
|
}
|
|
@@ -1117,6 +1113,7 @@ export default class Render {
|
|
|
1117
1113
|
// Create option
|
|
1118
1114
|
const optionEl = document.createElement('div')
|
|
1119
1115
|
optionEl.dataset.id = option.id // Dataset id for identifying an option
|
|
1116
|
+
optionEl.id = option.id
|
|
1120
1117
|
optionEl.classList.add(this.classes.option)
|
|
1121
1118
|
optionEl.setAttribute('role', 'option') // WCAG attribute
|
|
1122
1119
|
if (option.class) {
|
|
@@ -1164,8 +1161,11 @@ export default class Render {
|
|
|
1164
1161
|
// If option is selected
|
|
1165
1162
|
if (option.selected) {
|
|
1166
1163
|
optionEl.classList.add(this.classes.selected)
|
|
1164
|
+
optionEl.setAttribute('aria-selected', 'true')
|
|
1165
|
+
this.main.main.setAttribute('aria-activedescendant', optionEl.id)
|
|
1167
1166
|
} else {
|
|
1168
1167
|
optionEl.classList.remove(this.classes.selected)
|
|
1168
|
+
optionEl.setAttribute('aria-selected', 'false')
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
1171
|
// Add click event listener
|
|
@@ -10,8 +10,7 @@ export default class Settings {
|
|
|
10
10
|
// Dynamic settings
|
|
11
11
|
public isMultiple: boolean = false
|
|
12
12
|
public isOpen: boolean = false
|
|
13
|
-
public
|
|
14
|
-
public triggerFocus: boolean = true
|
|
13
|
+
public isFullOpen: boolean = false
|
|
15
14
|
public intervalMove: NodeJS.Timeout | null = null
|
|
16
15
|
|
|
17
16
|
// Fields set from constructor
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function generateID(): string;
|
|
2
2
|
export declare function hasClassInTree(element: HTMLElement, className: string): any;
|
|
3
|
-
export declare function debounce
|
|
3
|
+
export declare function debounce<T extends (...args: any[]) => void>(func: T, wait?: number, immediate?: boolean): () => void;
|
|
4
4
|
export declare function isEqual(a: any, b: any): boolean;
|
|
5
5
|
export declare function kebabCase(str: string): string;
|
|
@@ -36,7 +36,7 @@ export default class SlimSelect {
|
|
|
36
36
|
setSelected(value: string | string[], runAfterChange?: boolean): void;
|
|
37
37
|
addOption(option: OptionOptional): void;
|
|
38
38
|
open(): void;
|
|
39
|
-
close(): void;
|
|
39
|
+
close(eventType?: string | null): void;
|
|
40
40
|
search(value: string): void;
|
|
41
41
|
destroy(): void;
|
|
42
42
|
private windowResize;
|
|
@@ -91,8 +91,9 @@ export default class Render {
|
|
|
91
91
|
open(): void;
|
|
92
92
|
close(): void;
|
|
93
93
|
updateClassStyles(): void;
|
|
94
|
+
updateAriaAttributes(): void;
|
|
94
95
|
mainDiv(): Main;
|
|
95
|
-
mainFocus(
|
|
96
|
+
mainFocus(eventType: string | null): void;
|
|
96
97
|
placeholder(): HTMLDivElement;
|
|
97
98
|
renderValues(): void;
|
|
98
99
|
private renderSingleValue;
|
|
@@ -101,7 +102,7 @@ export default class Render {
|
|
|
101
102
|
contentDiv(): Content;
|
|
102
103
|
moveContent(): void;
|
|
103
104
|
searchDiv(): Search;
|
|
104
|
-
searchFocus(
|
|
105
|
+
searchFocus(): void;
|
|
105
106
|
getOptions(notPlaceholder?: boolean, notDisabled?: boolean, notHidden?: boolean): HTMLDivElement[];
|
|
106
107
|
highlight(dir: 'up' | 'down'): void;
|
|
107
108
|
listDiv(): HTMLDivElement;
|