slim-select 2.5.1 → 2.7.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/render.d.ts +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/slimselect.cjs.js +52 -10
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +52 -10
- package/dist/slimselect.global.js +52 -10
- package/dist/slimselect.js +52 -10
- package/dist/slimselect.min.js +1 -1
- package/dist/slimselect.umd.js +52 -10
- package/dist/slimselect.umd.min.js +1 -1
- package/dist/store.d.ts +4 -1
- package/index.html +4 -0
- package/package.json +51 -26
- package/src/react/.eslintignore +2 -0
- package/src/react/.eslintrc.cjs +24 -0
- package/src/react/index.html +14 -0
- package/src/react/jestconfig.json +11 -0
- package/src/react/package.json +32 -0
- package/src/react/src/frameworkpage.tsx +133 -0
- package/src/react/src/index.ts +3 -0
- package/src/react/src/slimselect.tsx +148 -0
- package/src/react/tests/slimselect.test.tsx +52 -0
- package/src/react/tsconfig.json +37 -0
- package/src/react/tsconfig.node.json +10 -0
- package/src/react/vite.config.ts +55 -0
- package/src/slim-select/index.test.ts +2 -2
- package/src/slim-select/render.ts +49 -10
- package/src/slim-select/select.ts +4 -2
- package/src/slim-select/settings.ts +2 -0
- package/src/slim-select/slimselect.scss +3 -2
- package/src/slim-select/store.ts +23 -3
- package/src/vue/dist/slim-select/helpers.d.ts +5 -5
- package/src/vue/dist/slim-select/index.d.ts +46 -46
- package/src/vue/dist/slim-select/render.d.ts +120 -120
- package/src/vue/dist/slim-select/select.d.ts +28 -28
- package/src/vue/dist/slim-select/settings.d.ts +33 -32
- package/src/vue/dist/slim-select/store.d.ts +80 -77
- package/src/vue/dist/slimselectvue.es.js +1940 -1903
- package/src/vue/dist/slimselectvue.global.js +1940 -1903
- package/src/vue/dist/slimselectvue.ssr.js +1940 -1903
- package/src/vue/dist/vue/hello.d.ts +2 -2
- package/src/vue/dist/vue/slimselect.vue.d.ts +324 -317
- package/src/vue/package.json +1 -1
- package/src/vue/slimselect.vue +2 -7
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
import SlimSelect, { Events, Config } from '../../slim-select'
|
|
5
|
+
import { SettingsPartial } from '../../slim-select/settings'
|
|
6
|
+
import { DataArrayPartial, Option } from '../../slim-select/store'
|
|
7
|
+
import '../../slim-select/slimselect.scss'
|
|
8
|
+
|
|
9
|
+
export type SlimSelectProps = {
|
|
10
|
+
style?: React.CSSProperties
|
|
11
|
+
id?: string | undefined
|
|
12
|
+
modelValue?: string | string[] | undefined
|
|
13
|
+
multiple?: boolean
|
|
14
|
+
data?: DataArrayPartial
|
|
15
|
+
settings?: SettingsPartial
|
|
16
|
+
events?: Events
|
|
17
|
+
children?: React.ReactNode
|
|
18
|
+
}
|
|
19
|
+
export interface SlimSelectRef {
|
|
20
|
+
set: (newValue: string | string[]) => void
|
|
21
|
+
// Potentially call other functions on SlimSelect that have not been exposed via Ref yet
|
|
22
|
+
getSlimSelectInstance: () => SlimSelect
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const SlimSelectComponent = (props: SlimSelectProps, ref: React.Ref<any>): JSX.Element => {
|
|
26
|
+
const { id = undefined, modelValue, style = {}, multiple = false, data, settings, events, children } = props
|
|
27
|
+
|
|
28
|
+
const slimHTMLElement = useRef(null)
|
|
29
|
+
const slimSelect = useRef<SlimSelect>()
|
|
30
|
+
const value = useRef<any>()
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const config = {
|
|
34
|
+
select: slimHTMLElement.current as unknown as Element,
|
|
35
|
+
events: {},
|
|
36
|
+
} as Config
|
|
37
|
+
|
|
38
|
+
// If data is passed in, use it
|
|
39
|
+
if (data) {
|
|
40
|
+
config.data = data
|
|
41
|
+
}
|
|
42
|
+
// If settings are passed in, use it
|
|
43
|
+
if (settings) {
|
|
44
|
+
config.settings = settings
|
|
45
|
+
}
|
|
46
|
+
// If events are passed in, use it
|
|
47
|
+
if (events) {
|
|
48
|
+
config.events = events
|
|
49
|
+
}
|
|
50
|
+
// Satisfy tsc
|
|
51
|
+
if (config.events == undefined) {
|
|
52
|
+
config.events = {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Wrap config.events.afterChange to run value update
|
|
56
|
+
const originalPropAfterChange = config.events.afterChange
|
|
57
|
+
|
|
58
|
+
config.events.afterChange = function (newSelectedOption: Option[]): void {
|
|
59
|
+
if (Array.isArray(newSelectedOption) && newSelectedOption.length > 0) {
|
|
60
|
+
const stringValue = multiple ? newSelectedOption.map((option) => option.value) : newSelectedOption[0].value
|
|
61
|
+
if (value.current !== stringValue) {
|
|
62
|
+
value.current = stringValue
|
|
63
|
+
}
|
|
64
|
+
// If they had an original afterChange, run it
|
|
65
|
+
if (config.events && originalPropAfterChange && typeof originalPropAfterChange === 'function') {
|
|
66
|
+
originalPropAfterChange(newSelectedOption)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Initialize SlimSelect
|
|
72
|
+
slimSelect.current = new SlimSelect(config)
|
|
73
|
+
|
|
74
|
+
// Get SlimSelect selected values
|
|
75
|
+
const selected = multiple
|
|
76
|
+
? (slimSelect.current as SlimSelect).getSelected()
|
|
77
|
+
: (slimSelect.current as SlimSelect).getSelected()[0]
|
|
78
|
+
|
|
79
|
+
// Check if modelValue is the same as the value of the select
|
|
80
|
+
if (selected !== modelValue && modelValue) {
|
|
81
|
+
// If not, set the value of the select to the modelValue
|
|
82
|
+
;(slimSelect.current as SlimSelect).setSelected(modelValue)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Destroy SlimSelect on unmount
|
|
86
|
+
return () => {
|
|
87
|
+
if (slimSelect.current) {
|
|
88
|
+
;(slimSelect.current as SlimSelect)?.destroy()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}, [modelValue, multiple, data, settings, events, children])
|
|
92
|
+
|
|
93
|
+
const getCleanValue = useCallback(
|
|
94
|
+
(val: string | string[] | undefined): string | string[] => {
|
|
95
|
+
const multi = multiple
|
|
96
|
+
|
|
97
|
+
// If its multiple and the modelValue is a string, return an array with the string
|
|
98
|
+
if (typeof val === 'string') {
|
|
99
|
+
return multi ? [val] : val
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If its not multiple and the modelValue is an array, return the first item
|
|
103
|
+
if (Array.isArray(val)) {
|
|
104
|
+
return multi ? val : val[0]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return multi ? [] : ''
|
|
108
|
+
},
|
|
109
|
+
[multiple],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (modelValue) {
|
|
114
|
+
;(slimSelect.current as SlimSelect)?.setSelected(getCleanValue(modelValue))
|
|
115
|
+
}
|
|
116
|
+
}, [modelValue, getCleanValue])
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (data) {
|
|
120
|
+
;(slimSelect.current as SlimSelect)?.setData(data)
|
|
121
|
+
}
|
|
122
|
+
}, [data])
|
|
123
|
+
|
|
124
|
+
useImperativeHandle(
|
|
125
|
+
ref,
|
|
126
|
+
() => {
|
|
127
|
+
return {
|
|
128
|
+
set(newValue: string | string[]): void {
|
|
129
|
+
;(slimSelect.current as SlimSelect)?.setSelected(newValue)
|
|
130
|
+
},
|
|
131
|
+
getSlimSelectInstance(): SlimSelect {
|
|
132
|
+
return slimSelect.current as SlimSelect
|
|
133
|
+
},
|
|
134
|
+
} as SlimSelectRef
|
|
135
|
+
},
|
|
136
|
+
[],
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<>
|
|
141
|
+
<select style={style} id={id} data-testid={id} multiple={multiple} ref={slimHTMLElement}>
|
|
142
|
+
{children}
|
|
143
|
+
</select>
|
|
144
|
+
</>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default forwardRef(SlimSelectComponent)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { render } from '@testing-library/react'
|
|
4
|
+
import userEvent from '@testing-library/user-event'
|
|
5
|
+
import '@testing-library/jest-dom/extend-expect'
|
|
6
|
+
|
|
7
|
+
import 'jest-canvas-mock'
|
|
8
|
+
|
|
9
|
+
import SlimSelectComponent from '../src/slimselect'
|
|
10
|
+
|
|
11
|
+
describe('Slim Select', () => {
|
|
12
|
+
console.log(React.version)
|
|
13
|
+
it('renders successfully', () => {
|
|
14
|
+
const { queryByTestId, queryAllByText, getByText } = render(
|
|
15
|
+
<SlimSelectComponent id="slimselect" ref={null} multiple={true}>
|
|
16
|
+
<option value={'value1'}>Value 1</option>
|
|
17
|
+
<option value={'value2'}>Value 2</option>
|
|
18
|
+
<option value={'value3'}>Value 3</option>
|
|
19
|
+
</SlimSelectComponent>,
|
|
20
|
+
)
|
|
21
|
+
expect(queryByTestId('slimselect')).not.toEqual(null)
|
|
22
|
+
expect(queryByTestId('slimselect')).not.toBeVisible()
|
|
23
|
+
expect(getByText('Select Value')).not.toEqual(null)
|
|
24
|
+
expect(queryAllByText('Value 1')[1].getAttribute('aria-selected')).toEqual('false')
|
|
25
|
+
expect(queryAllByText('Value 2')[1].getAttribute('aria-selected')).toEqual('false')
|
|
26
|
+
expect(queryAllByText('Value 3')[1].getAttribute('aria-selected')).toEqual('false')
|
|
27
|
+
})
|
|
28
|
+
it('can be selected with onchange callback', async () => {
|
|
29
|
+
const { queryAllByText } = render(
|
|
30
|
+
<SlimSelectComponent id="slimselect" ref={null} multiple={true}>
|
|
31
|
+
<option value={'value1'}>Value 1</option>
|
|
32
|
+
<option value={'value2'}>Value 2</option>
|
|
33
|
+
<option value={'value3'}>Value 3</option>
|
|
34
|
+
</SlimSelectComponent>,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// First original html select element gets hidden, after comes slimselect div - that is why array index = 1
|
|
38
|
+
const firstSlimOption = queryAllByText('Value 1')[1]
|
|
39
|
+
const thirdSlimOpption = queryAllByText('Value 3')[1]
|
|
40
|
+
|
|
41
|
+
expect(firstSlimOption).not.toBeUndefined()
|
|
42
|
+
expect(thirdSlimOpption).not.toBeUndefined()
|
|
43
|
+
|
|
44
|
+
await userEvent.click(firstSlimOption as HTMLElement)
|
|
45
|
+
await userEvent.click(thirdSlimOpption as HTMLElement)
|
|
46
|
+
|
|
47
|
+
expect(queryAllByText('Select Value')).toHaveLength(0)
|
|
48
|
+
expect(queryAllByText('Value 1')[1].getAttribute('aria-selected')).toEqual('true')
|
|
49
|
+
expect(queryAllByText('Value 2')[1].getAttribute('aria-selected')).toEqual('false')
|
|
50
|
+
expect(queryAllByText('Value 3')[1].getAttribute('aria-selected')).toEqual('true')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exclude": ["dist", "node_modules"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"importHelpers": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
//"rootDir": "./src",
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
|
|
16
|
+
/* Bundler mode */
|
|
17
|
+
"moduleResolution": "node",
|
|
18
|
+
"allowImportingTsExtensions": true,
|
|
19
|
+
"resolveJsonModule": true,
|
|
20
|
+
"isolatedModules": true,
|
|
21
|
+
"noEmit": true,
|
|
22
|
+
|
|
23
|
+
"jsx": "react-jsx",
|
|
24
|
+
|
|
25
|
+
/* Linting */
|
|
26
|
+
"strict": true,
|
|
27
|
+
"noFallthroughCasesInSwitch": true,
|
|
28
|
+
"noImplicitReturns": true,
|
|
29
|
+
"noUnusedLocals": false,
|
|
30
|
+
"noUnusedParameters": false,
|
|
31
|
+
"paths": {
|
|
32
|
+
"react": ["./node_modules/@types/react"]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"include": ["./src"],
|
|
36
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
37
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import react from '@vitejs/plugin-react'
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
import { babel } from '@rollup/plugin-babel'
|
|
4
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
5
|
+
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
|
|
6
|
+
import dts from 'vite-plugin-dts'
|
|
7
|
+
|
|
8
|
+
const formatNameMap = {
|
|
9
|
+
iife: 'global',
|
|
10
|
+
cjs: 'ssr',
|
|
11
|
+
es: 'es',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// https://vitejs.dev/config/
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
plugins: [
|
|
17
|
+
react(),
|
|
18
|
+
dts({
|
|
19
|
+
insertTypesEntry: true,
|
|
20
|
+
skipDiagnostics: true,
|
|
21
|
+
}),
|
|
22
|
+
],
|
|
23
|
+
build: {
|
|
24
|
+
target: 'modules',
|
|
25
|
+
lib: {
|
|
26
|
+
// Could also be a dictionary or array of multiple entry points
|
|
27
|
+
entry: 'src/index.ts',
|
|
28
|
+
formats: ['es', 'cjs', 'iife'],
|
|
29
|
+
name: 'SlimSelectReact',
|
|
30
|
+
// the proper extensions will be added
|
|
31
|
+
fileName: (format) => `slimselectreact.${formatNameMap[format]}.js`,
|
|
32
|
+
},
|
|
33
|
+
rollupOptions: {
|
|
34
|
+
// make sure to externalize deps that shouldn't be bundled
|
|
35
|
+
// into your library
|
|
36
|
+
// external: ['react', 'react-dom'],
|
|
37
|
+
plugins: [
|
|
38
|
+
peerDepsExternal(),
|
|
39
|
+
babel({
|
|
40
|
+
babelHelpers: 'bundled',
|
|
41
|
+
presets: ['@babel/preset-env', '@babel/preset-react'],
|
|
42
|
+
exclude: '../../node_modules/',
|
|
43
|
+
}),
|
|
44
|
+
commonjs(),
|
|
45
|
+
],
|
|
46
|
+
output: {
|
|
47
|
+
// for externalized deps
|
|
48
|
+
globals: {
|
|
49
|
+
react: 'React',
|
|
50
|
+
'react-dom': 'ReactDOM',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
})
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
'use strict'
|
|
6
6
|
import { describe, expect, test } from '@jest/globals'
|
|
7
7
|
import SlimSelect from './'
|
|
8
|
-
import { OptionOptional } from '
|
|
9
|
-
import { Config } from '
|
|
8
|
+
import { OptionOptional } from './store'
|
|
9
|
+
import { Config } from './index'
|
|
10
10
|
|
|
11
11
|
describe('SlimSelect Module', () => {
|
|
12
12
|
test('constructor', () => {
|
|
@@ -6,7 +6,7 @@ export interface Callbacks {
|
|
|
6
6
|
open: () => void
|
|
7
7
|
close: () => void
|
|
8
8
|
addable?: (value: string) => Promise<OptionOptional | string> | OptionOptional | string | false | undefined | null
|
|
9
|
-
setSelected: (value: string[], runAfterChange: boolean) => void
|
|
9
|
+
setSelected: (value: string | string[], runAfterChange: boolean) => void
|
|
10
10
|
addOption: (option: Option) => void
|
|
11
11
|
search: (search: string) => void
|
|
12
12
|
beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void
|
|
@@ -230,7 +230,10 @@ export default class Render {
|
|
|
230
230
|
|
|
231
231
|
// Add id to data-id
|
|
232
232
|
main.dataset.id = this.settings.id
|
|
233
|
-
main.id = this.settings.id
|
|
233
|
+
// main.id = this.settings.id+'-main' // Remove for now as it is not needed
|
|
234
|
+
|
|
235
|
+
// Add label
|
|
236
|
+
main.setAttribute('aria-label', this.settings.ariaLabel)
|
|
234
237
|
|
|
235
238
|
// Set tabable to allow tabbing to the element
|
|
236
239
|
main.tabIndex = 0
|
|
@@ -238,7 +241,7 @@ export default class Render {
|
|
|
238
241
|
// Deal with keyboard events on the main div
|
|
239
242
|
// This is to allow for normal selecting
|
|
240
243
|
// when you may not have a search bar
|
|
241
|
-
main.onkeydown = (e: KeyboardEvent) => {
|
|
244
|
+
main.onkeydown = (e: KeyboardEvent): boolean => {
|
|
242
245
|
// Convert above if else statemets to switch
|
|
243
246
|
switch (e.key) {
|
|
244
247
|
case 'ArrowUp':
|
|
@@ -261,10 +264,12 @@ export default class Render {
|
|
|
261
264
|
this.callbacks.close()
|
|
262
265
|
return false
|
|
263
266
|
}
|
|
267
|
+
|
|
268
|
+
return false
|
|
264
269
|
}
|
|
265
270
|
|
|
266
271
|
// Add onclick for main div
|
|
267
|
-
main.onclick = (e: Event) => {
|
|
272
|
+
main.onclick = (e: Event): void => {
|
|
268
273
|
// Dont do anything if disabled
|
|
269
274
|
if (this.settings.disabled) {
|
|
270
275
|
return
|
|
@@ -314,7 +319,11 @@ export default class Render {
|
|
|
314
319
|
this.callbacks.setSelected([], false)
|
|
315
320
|
this.updateDeselectAll()
|
|
316
321
|
} else {
|
|
317
|
-
|
|
322
|
+
// Get first option and set it as selected
|
|
323
|
+
const firstOption = this.store.getFirstOption()
|
|
324
|
+
const value = firstOption ? firstOption.value : ''
|
|
325
|
+
|
|
326
|
+
this.callbacks.setSelected(value, false)
|
|
318
327
|
}
|
|
319
328
|
|
|
320
329
|
// Check if we need to close the dropdown
|
|
@@ -324,7 +333,7 @@ export default class Render {
|
|
|
324
333
|
|
|
325
334
|
// Run afterChange callback
|
|
326
335
|
if (this.callbacks.afterChange) {
|
|
327
|
-
this.callbacks.afterChange(
|
|
336
|
+
this.callbacks.afterChange(this.store.getSelectedOptions())
|
|
328
337
|
}
|
|
329
338
|
}
|
|
330
339
|
}
|
|
@@ -624,7 +633,7 @@ export default class Render {
|
|
|
624
633
|
|
|
625
634
|
// Add id to data-id
|
|
626
635
|
main.dataset.id = this.settings.id
|
|
627
|
-
main.id = this.settings.id
|
|
636
|
+
// main.id = this.settings.id + '-content' // Remove for now as it is not needed
|
|
628
637
|
|
|
629
638
|
// Add search
|
|
630
639
|
const search = this.searchDiv()
|
|
@@ -696,7 +705,7 @@ export default class Render {
|
|
|
696
705
|
}, 100)
|
|
697
706
|
|
|
698
707
|
// Deal with keyboard events on search input field
|
|
699
|
-
input.onkeydown = (e: KeyboardEvent) => {
|
|
708
|
+
input.onkeydown = (e: KeyboardEvent): boolean => {
|
|
700
709
|
// Convert above if else statemets to switch
|
|
701
710
|
switch (e.key) {
|
|
702
711
|
case 'ArrowUp':
|
|
@@ -727,6 +736,8 @@ export default class Render {
|
|
|
727
736
|
}
|
|
728
737
|
return true
|
|
729
738
|
}
|
|
739
|
+
|
|
740
|
+
return true // Allow normal typing
|
|
730
741
|
}
|
|
731
742
|
|
|
732
743
|
main.appendChild(input)
|
|
@@ -867,15 +878,34 @@ export default class Render {
|
|
|
867
878
|
|
|
868
879
|
// If length is 1, highlight it
|
|
869
880
|
if (options.length === 1) {
|
|
870
|
-
// Check if option doesnt already
|
|
881
|
+
// Check if option doesnt already have highlighted class
|
|
871
882
|
if (!options[0].classList.contains(this.classes.highlighted)) {
|
|
872
883
|
options[0].classList.add(this.classes.highlighted)
|
|
873
884
|
return
|
|
874
885
|
}
|
|
875
886
|
}
|
|
876
887
|
|
|
888
|
+
// Loop through options and see if there are no highlighted ones
|
|
889
|
+
let highlighted = false
|
|
890
|
+
for (const o of options) {
|
|
891
|
+
if (o.classList.contains(this.classes.highlighted)) {
|
|
892
|
+
highlighted = true
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// If no highlighted, see if any are selected and if so highlight selected first one
|
|
897
|
+
if (!highlighted) {
|
|
898
|
+
for (const o of options) {
|
|
899
|
+
if (o.classList.contains(this.classes.selected)) {
|
|
900
|
+
o.classList.add(this.classes.highlighted)
|
|
901
|
+
break
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
877
906
|
// Loop through options and find the highlighted one
|
|
878
907
|
for (let i = 0; i < options.length; i++) {
|
|
908
|
+
// Found highlighted option
|
|
879
909
|
if (options[i].classList.contains(this.classes.highlighted)) {
|
|
880
910
|
const prevOption = options[i]
|
|
881
911
|
// Remove highlighted class from current one
|
|
@@ -1005,7 +1035,7 @@ export default class Render {
|
|
|
1005
1035
|
|
|
1006
1036
|
// Add select all text span
|
|
1007
1037
|
const selectAllText = document.createElement('span')
|
|
1008
|
-
selectAllText.textContent =
|
|
1038
|
+
selectAllText.textContent = d.selectAllText
|
|
1009
1039
|
selectAll.appendChild(selectAllText)
|
|
1010
1040
|
|
|
1011
1041
|
// Create new svg for checkbox
|
|
@@ -1051,7 +1081,16 @@ export default class Render {
|
|
|
1051
1081
|
// Put together new list with all options in this optgroup
|
|
1052
1082
|
const newSelected = currentSelected.concat(d.options.map((o) => o.value))
|
|
1053
1083
|
|
|
1084
|
+
// Loop through options and if they don't exist in the store
|
|
1085
|
+
// run addOption callback
|
|
1086
|
+
for (const o of d.options) {
|
|
1087
|
+
if (!this.store.getOptionByID(o.id)) {
|
|
1088
|
+
this.callbacks.addOption(o)
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1054
1092
|
this.callbacks.setSelected(newSelected, true)
|
|
1093
|
+
return
|
|
1055
1094
|
}
|
|
1056
1095
|
})
|
|
1057
1096
|
|
|
@@ -17,8 +17,9 @@ export default class Select {
|
|
|
17
17
|
constructor(select: HTMLSelectElement) {
|
|
18
18
|
this.select = select
|
|
19
19
|
|
|
20
|
+
this.valueChange = this.valueChange.bind(this)
|
|
20
21
|
// Add change event listener
|
|
21
|
-
this.select.addEventListener('change', this.valueChange
|
|
22
|
+
this.select.addEventListener('change', this.valueChange, {
|
|
22
23
|
// allow bubbling of event
|
|
23
24
|
passive: true,
|
|
24
25
|
})
|
|
@@ -160,6 +161,7 @@ export default class Select {
|
|
|
160
161
|
id: optgroup.id,
|
|
161
162
|
label: optgroup.label,
|
|
162
163
|
selectAll: optgroup.dataset ? optgroup.dataset.selectall === 'true' : false,
|
|
164
|
+
selectAllText: optgroup.dataset ? optgroup.dataset.selectalltext : 'Select all',
|
|
163
165
|
closable: optgroup.dataset ? optgroup.dataset.closable : 'off',
|
|
164
166
|
options: [],
|
|
165
167
|
} as OptgroupOptional
|
|
@@ -360,7 +362,7 @@ export default class Select {
|
|
|
360
362
|
this.changeListen(false)
|
|
361
363
|
|
|
362
364
|
// Remove event change listener
|
|
363
|
-
this.select.removeEventListener('change', this.valueChange
|
|
365
|
+
this.select.removeEventListener('change', this.valueChange)
|
|
364
366
|
|
|
365
367
|
// Disconnect observer and null
|
|
366
368
|
if (this.observer) {
|
|
@@ -17,6 +17,7 @@ export default class Settings {
|
|
|
17
17
|
public disabled: boolean
|
|
18
18
|
public alwaysOpen: boolean
|
|
19
19
|
public showSearch: boolean
|
|
20
|
+
public ariaLabel: string
|
|
20
21
|
public searchPlaceholder: string
|
|
21
22
|
public searchText: string
|
|
22
23
|
public searchingText: string
|
|
@@ -47,6 +48,7 @@ export default class Settings {
|
|
|
47
48
|
this.disabled = settings.disabled !== undefined ? settings.disabled : false
|
|
48
49
|
this.alwaysOpen = settings.alwaysOpen !== undefined ? settings.alwaysOpen : false
|
|
49
50
|
this.showSearch = settings.showSearch !== undefined ? settings.showSearch : true
|
|
51
|
+
this.ariaLabel = settings.ariaLabel || 'Combobox'
|
|
50
52
|
this.searchPlaceholder = settings.searchPlaceholder || 'Search'
|
|
51
53
|
this.searchText = settings.searchText || 'No Results'
|
|
52
54
|
this.searchingText = settings.searchingText || 'Searching...'
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
--ss-highlight-color: #fffb8c;
|
|
10
10
|
--ss-success-color: #00b755;
|
|
11
11
|
--ss-error-color: #dc3545;
|
|
12
|
+
--ss-focus-color: #5897fb;
|
|
12
13
|
|
|
13
14
|
// Heights
|
|
14
15
|
--ss-main-height: 30px;
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
overflow: hidden;
|
|
72
73
|
|
|
73
74
|
&:focus {
|
|
74
|
-
box-shadow: 0 0 5px var(--ss-
|
|
75
|
+
box-shadow: 0 0 5px var(--ss-focus-color);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
&.ss-disabled {
|
|
@@ -303,7 +304,7 @@
|
|
|
303
304
|
}
|
|
304
305
|
|
|
305
306
|
&:focus {
|
|
306
|
-
box-shadow: 0 0 5px var(--ss-
|
|
307
|
+
box-shadow: 0 0 5px var(--ss-focus-color);
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
310
|
|
package/src/slim-select/store.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface OptgroupOptional {
|
|
|
12
12
|
id?: string
|
|
13
13
|
label: string // Required
|
|
14
14
|
selectAll?: boolean
|
|
15
|
+
selectAllText?: string
|
|
15
16
|
closable?: 'off' | 'open' | 'close'
|
|
16
17
|
options?: OptionOptional[]
|
|
17
18
|
}
|
|
@@ -20,6 +21,7 @@ export class Optgroup {
|
|
|
20
21
|
public id: string
|
|
21
22
|
public label: string
|
|
22
23
|
public selectAll: boolean
|
|
24
|
+
public selectAllText: string
|
|
23
25
|
public closable: 'off' | 'open' | 'close'
|
|
24
26
|
public options: Option[]
|
|
25
27
|
|
|
@@ -27,6 +29,7 @@ export class Optgroup {
|
|
|
27
29
|
this.id = !optgroup.id || optgroup.id === '' ? generateID() : optgroup.id
|
|
28
30
|
this.label = optgroup.label || ''
|
|
29
31
|
this.selectAll = optgroup.selectAll === undefined ? false : optgroup.selectAll
|
|
32
|
+
this.selectAllText = optgroup.selectAllText || 'Select All'
|
|
30
33
|
this.closable = optgroup.closable || 'off'
|
|
31
34
|
|
|
32
35
|
// If options exist, loop through options and create new option class
|
|
@@ -281,6 +284,26 @@ export default class Store {
|
|
|
281
284
|
return options.length ? options[0] : null
|
|
282
285
|
}
|
|
283
286
|
|
|
287
|
+
public getSelectType(): string {
|
|
288
|
+
return this.selectType
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public getFirstOption(): Option | null {
|
|
292
|
+
let option: Option | null = null
|
|
293
|
+
for (let dataObj of this.data) {
|
|
294
|
+
if (dataObj instanceof Optgroup) {
|
|
295
|
+
option = dataObj.options[0]
|
|
296
|
+
} else if (dataObj instanceof Option) {
|
|
297
|
+
option = dataObj
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (option) {
|
|
301
|
+
break
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return option
|
|
305
|
+
}
|
|
306
|
+
|
|
284
307
|
// Take in search string and return filtered list of values
|
|
285
308
|
public search(search: string, searchFilter: (opt: Option, search: string) => boolean): DataArray {
|
|
286
309
|
search = search.trim()
|
|
@@ -339,7 +362,4 @@ export default class Store {
|
|
|
339
362
|
return dataSearch
|
|
340
363
|
}
|
|
341
364
|
|
|
342
|
-
public getSelectType(): string {
|
|
343
|
-
return this.selectType
|
|
344
|
-
}
|
|
345
365
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare function generateID(): string;
|
|
2
|
-
export declare function hasClassInTree(element: HTMLElement, className: string): any;
|
|
3
|
-
export declare function debounce<T extends (...args: any[]) => void>(func: T, wait?: number, immediate?: boolean): () => void;
|
|
4
|
-
export declare function isEqual(a: any, b: any): boolean;
|
|
5
|
-
export declare function kebabCase(str: string): string;
|
|
1
|
+
export declare function generateID(): string;
|
|
2
|
+
export declare function hasClassInTree(element: HTMLElement, className: string): any;
|
|
3
|
+
export declare function debounce<T extends (...args: any[]) => void>(func: T, wait?: number, immediate?: boolean): () => void;
|
|
4
|
+
export declare function isEqual(a: any, b: any): boolean;
|
|
5
|
+
export declare function kebabCase(str: string): string;
|