react-animated-select 0.2.8 → 0.3.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/README.md +70 -28
- package/demo/README.md +16 -0
- package/demo/eslint.config.js +29 -0
- package/demo/index.html +13 -0
- package/demo/package-lock.json +3436 -0
- package/demo/package.json +37 -0
- package/demo/public/vite.svg +1 -0
- package/demo/src/App.tsx +412 -0
- package/demo/src/main.jsx +9 -0
- package/demo/src/rac.css +754 -0
- package/demo/src/shake.js +11 -0
- package/demo/src/slideDown.jsx +35 -0
- package/demo/vite.config.js +7 -0
- package/dist/index.cjs.js +6 -9
- package/dist/index.css +1 -1
- package/dist/index.es.js +714 -609
- package/index.d.ts +10 -1
- package/package.json +3 -2
- package/src/option.jsx +5 -1
- package/src/select.css +7 -0
- package/src/select.jsx +72 -39
- package/src/useSelect.jsx +77 -8
- package/src/useSelectLogic.jsx +163 -174
package/src/useSelectLogic.jsx
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {useState, useMemo, useCallback, useId} from 'react'
|
|
1
|
+
import {useState, useMemo, useCallback, useId, useEffect} from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function useSelectLogic({
|
|
4
4
|
options = [],
|
|
5
5
|
jsxOptions = [],
|
|
6
|
-
|
|
7
6
|
value,
|
|
8
7
|
defaultValue = undefined,
|
|
9
8
|
onChange,
|
|
10
|
-
|
|
11
9
|
disabled = false,
|
|
12
10
|
loading = false,
|
|
13
11
|
error = false,
|
|
14
|
-
|
|
15
12
|
placeholder = 'Choose option',
|
|
16
13
|
emptyText = 'No options',
|
|
17
14
|
disabledText = 'Disabled',
|
|
@@ -20,71 +17,60 @@ export function useSelectLogic({
|
|
|
20
17
|
disabledOption = 'Disabled option',
|
|
21
18
|
emptyOption = 'Empty option',
|
|
22
19
|
invalidOption = 'Invalid option',
|
|
23
|
-
setVisibility
|
|
20
|
+
setVisibility,
|
|
21
|
+
hasMore,
|
|
22
|
+
loadButton,
|
|
23
|
+
setLoadingTitle,
|
|
24
|
+
loadingTitle,
|
|
25
|
+
loadMoreText,
|
|
26
|
+
loadMore,
|
|
27
|
+
childrenFirst
|
|
24
28
|
}) {
|
|
25
|
-
|
|
26
29
|
const stableId = useId()
|
|
27
|
-
|
|
28
|
-
// controlled select check
|
|
29
30
|
const isControlled = value !== undefined
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const [internalValue, setInternalValue] = useState(defaultValue)
|
|
33
|
-
|
|
34
|
-
// value chosenness status
|
|
35
|
-
const selectedValue = isControlled ? value : internalValue
|
|
31
|
+
|
|
32
|
+
const [selectedId, setSelectedId] = useState(null)
|
|
36
33
|
|
|
37
34
|
const isOptionObject = (obj) => {
|
|
38
35
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
|
|
39
|
-
return (
|
|
40
|
-
'id' in obj ||
|
|
41
|
-
'value' in obj ||
|
|
42
|
-
'name' in obj ||
|
|
43
|
-
'label' in obj ||
|
|
44
|
-
'disabled' in obj
|
|
45
|
-
)
|
|
36
|
+
return ('id' in obj || 'value' in obj || 'name' in obj || 'label' in obj || 'disabled' in obj)
|
|
46
37
|
}
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
const normalizedPropOptions = useMemo(() => {
|
|
50
|
-
if (!options) return []
|
|
51
|
-
|
|
39
|
+
const normalizedOptions = useMemo(() => {
|
|
52
40
|
const flat = []
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
label: '0',
|
|
66
|
-
original: originalItem
|
|
41
|
+
const push = (key, val, originalItem) => {
|
|
42
|
+
let computedUserId = originalItem?.id ?? originalItem?.value ?? val ?? key
|
|
43
|
+
|
|
44
|
+
if (typeof val === 'function') {
|
|
45
|
+
flat.push({
|
|
46
|
+
key: `invalid-${flat.length}`,
|
|
47
|
+
value: val,
|
|
48
|
+
userId: null,
|
|
49
|
+
disabled: true,
|
|
50
|
+
invalid: true,
|
|
51
|
+
label: invalidOption,
|
|
52
|
+
original: originalItem
|
|
67
53
|
})
|
|
68
54
|
return
|
|
69
55
|
}
|
|
70
56
|
|
|
71
|
-
if (
|
|
57
|
+
if (val === '') {
|
|
72
58
|
flat.push({
|
|
73
|
-
key: `
|
|
74
|
-
value,
|
|
75
|
-
userId:
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
key: `empty-str-${flat.length}`,
|
|
60
|
+
value: '',
|
|
61
|
+
userId: null,
|
|
62
|
+
disabled: true,
|
|
63
|
+
label: emptyOption,
|
|
78
64
|
original: originalItem
|
|
79
65
|
})
|
|
80
66
|
return
|
|
81
67
|
}
|
|
82
68
|
|
|
83
|
-
if (
|
|
69
|
+
if (val === null || val === undefined) {
|
|
84
70
|
flat.push({
|
|
85
71
|
key: `empty-${flat.length}`,
|
|
86
72
|
value: null,
|
|
87
|
-
userId:
|
|
73
|
+
userId: null,
|
|
88
74
|
disabled: true,
|
|
89
75
|
label: emptyOption,
|
|
90
76
|
original: originalItem
|
|
@@ -92,177 +78,180 @@ export function useSelectLogic({
|
|
|
92
78
|
return
|
|
93
79
|
}
|
|
94
80
|
|
|
95
|
-
if (typeof
|
|
96
|
-
flat.push({
|
|
97
|
-
key:
|
|
98
|
-
value:
|
|
99
|
-
userId: computedUserId
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
original: originalItem
|
|
81
|
+
if (typeof val === 'number' || typeof val === 'boolean') {
|
|
82
|
+
flat.push({
|
|
83
|
+
key: `${typeof val}-${val}-${flat.length}`,
|
|
84
|
+
value: val,
|
|
85
|
+
userId: computedUserId,
|
|
86
|
+
label: String(val),
|
|
87
|
+
original: originalItem
|
|
103
88
|
})
|
|
104
89
|
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
108
|
-
flat.push({
|
|
109
|
-
key: value.id ?? value.value ?? value.name ?? key,
|
|
110
|
-
value,
|
|
111
|
-
userId: computedUserId,
|
|
112
|
-
disabled: !!value.disabled,
|
|
113
|
-
label: value.name ?? value.label ?? key,
|
|
114
|
-
original: originalItem
|
|
115
|
-
})
|
|
116
90
|
} else {
|
|
117
|
-
flat.push({
|
|
118
|
-
key
|
|
119
|
-
value,
|
|
120
|
-
userId: computedUserId,
|
|
121
|
-
label: String(
|
|
122
|
-
original: originalItem
|
|
91
|
+
flat.push({
|
|
92
|
+
key: key ?? `opt-${flat.length}`,
|
|
93
|
+
value: val,
|
|
94
|
+
userId: computedUserId,
|
|
95
|
+
label: String(val ?? key),
|
|
96
|
+
original: originalItem
|
|
123
97
|
})
|
|
124
98
|
}
|
|
125
99
|
}
|
|
126
100
|
|
|
127
101
|
if (Array.isArray(options)) {
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
item
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
Object.keys(item).length == 1 &&
|
|
134
|
-
item.disabled == true
|
|
135
|
-
) {
|
|
136
|
-
flat.push({
|
|
137
|
-
key: `disabled-${flat.length}`,
|
|
138
|
-
value: null,
|
|
139
|
-
userId: null,
|
|
140
|
-
disabled: true,
|
|
141
|
-
label: disabledOption,
|
|
142
|
-
original: item
|
|
143
|
-
})
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
102
|
+
options.forEach((item, index) => {
|
|
103
|
+
if (item && typeof item === 'object' && Object.keys(item).length === 1 && item.disabled === true) {
|
|
104
|
+
flat.push({key: `dis-${index}`, value: null, userId: null, disabled: true, label: disabledOption, original: item})
|
|
105
|
+
} else if (isOptionObject(item)) {
|
|
106
|
+
const stableUserId = item.id ?? (typeof item.value !== 'object' ? item.value : (item.label ?? item.name ?? item.value))
|
|
146
107
|
|
|
147
|
-
|
|
108
|
+
let rawLabel = item.name || item.label || item.id || item.value
|
|
148
109
|
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
if (rawLabel === null || rawLabel === undefined || rawLabel === '') {
|
|
111
|
+
const fallbackEntry = Object.entries(item).find(([k, v]) =>
|
|
112
|
+
k !== 'disabled' && v !== null && v !== undefined && v !== ''
|
|
113
|
+
)
|
|
114
|
+
if (fallbackEntry) {
|
|
115
|
+
rawLabel = fallbackEntry[1]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const hasNoContent = rawLabel === null || rawLabel === undefined || rawLabel === ''
|
|
120
|
+
const finalLabel = hasNoContent ? emptyOption : String(rawLabel)
|
|
151
121
|
|
|
152
122
|
flat.push({
|
|
153
|
-
key: item.id ?? item.value ?? item.name ?? `opt-${
|
|
154
|
-
value: item.value
|
|
123
|
+
key: item.id ?? item.value ?? item.name ?? `opt-${index}`,
|
|
124
|
+
value: item.value !== undefined ? item.value : (item.id !== undefined ? item.id : item),
|
|
155
125
|
userId: stableUserId,
|
|
156
|
-
disabled: !!item.disabled,
|
|
157
|
-
label:
|
|
126
|
+
disabled: hasNoContent || !!item.disabled,
|
|
127
|
+
label: finalLabel,
|
|
158
128
|
original: item
|
|
159
129
|
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
push(k, v, v)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
130
|
+
} else if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
131
|
+
Object.entries(item).forEach(([k, v]) => push(k, v, v))
|
|
132
|
+
} else {
|
|
167
133
|
push(item, item, item)
|
|
168
134
|
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
for (const [k, v] of Object.entries(options)) {
|
|
173
|
-
push(k, v, v)
|
|
174
|
-
}
|
|
135
|
+
})
|
|
136
|
+
} else if (typeof options === 'object' && options !== null) {
|
|
137
|
+
Object.entries(options).forEach(([k, v]) => push(k, v, v))
|
|
175
138
|
}
|
|
176
139
|
|
|
177
|
-
|
|
178
|
-
|
|
140
|
+
const propOpts = flat.map((item, i) => ({
|
|
141
|
+
id: `${stableId}-opt-${i}`,
|
|
142
|
+
userId: item.userId,
|
|
143
|
+
name: String(item.label),
|
|
144
|
+
raw: item.value,
|
|
145
|
+
original: item.original,
|
|
146
|
+
disabled: item.disabled,
|
|
147
|
+
invalid: item.invalid,
|
|
148
|
+
type: typeof item.value === 'boolean' ? 'boolean' : 'normal'
|
|
149
|
+
}))
|
|
150
|
+
|
|
151
|
+
const jsxOpts = jsxOptions.map((opt, index) => {
|
|
152
|
+
const hasNoValue = opt.value === null || opt.value === undefined
|
|
153
|
+
const hasNoLabel = opt.label === null || opt.label === undefined || opt.label === ''
|
|
154
|
+
|
|
155
|
+
const isActuallyEmpty = hasNoValue && hasNoLabel
|
|
179
156
|
|
|
180
157
|
return {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
raw: item.value,
|
|
185
|
-
original: item.original,
|
|
186
|
-
disabled: item.disabled,
|
|
187
|
-
type: typeof item.value === 'boolean' ? 'boolean' : 'normal'
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
}, [options, stableId])
|
|
192
|
-
|
|
193
|
-
const normalizedJsxOptions = useMemo(() => {
|
|
194
|
-
return jsxOptions.map((opt, index) => {
|
|
195
|
-
const uniqueId = `jsx-${stableId.replace(/:/g, '')}-${opt.id}-${index}`
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
id: uniqueId,
|
|
199
|
-
userId: opt.id,
|
|
200
|
-
value: opt.value,
|
|
158
|
+
...opt,
|
|
159
|
+
id: `jsx-${stableId.replace(/:/g, '')}-${opt.id}-${index}`,
|
|
160
|
+
userId: opt.id,
|
|
201
161
|
raw: opt.value,
|
|
202
162
|
original: opt.value,
|
|
203
|
-
name: opt.label,
|
|
204
|
-
|
|
205
|
-
disabled: opt.disabled,
|
|
206
|
-
className: opt.className,
|
|
163
|
+
name: isActuallyEmpty ? emptyOption : opt.label,
|
|
164
|
+
disabled: opt.disabled || isActuallyEmpty,
|
|
207
165
|
type: typeof opt.value === 'boolean' ? 'boolean' : 'normal'
|
|
208
166
|
}
|
|
209
167
|
})
|
|
210
|
-
}, [jsxOptions, stableId])
|
|
211
168
|
|
|
212
|
-
|
|
213
|
-
return [...normalizedPropOptions, ...normalizedJsxOptions]
|
|
214
|
-
}, [normalizedPropOptions, normalizedJsxOptions])
|
|
169
|
+
const combined = childrenFirst ? [...jsxOpts, ...propOpts] : [...propOpts, ...jsxOpts]
|
|
215
170
|
|
|
216
|
-
|
|
171
|
+
if (hasMore && loadButton) {
|
|
172
|
+
const isLoading = loadingTitle === loadMoreText
|
|
217
173
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
174
|
+
combined.push({
|
|
175
|
+
id: 'special-load-more-id',
|
|
176
|
+
name: loadingTitle,
|
|
177
|
+
loadMore: true,
|
|
178
|
+
loading: isLoading,
|
|
179
|
+
type: 'special'
|
|
180
|
+
})
|
|
181
|
+
}
|
|
221
182
|
|
|
222
|
-
|
|
223
|
-
|
|
183
|
+
return combined
|
|
184
|
+
}, [options, jsxOptions, stableId, emptyOption, disabledOption, hasMore, loadButton, loadingTitle, loadMoreText])
|
|
185
|
+
|
|
186
|
+
const findIdByValue = useCallback((val) => {
|
|
187
|
+
if (val === undefined || val === null) return null
|
|
224
188
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
189
|
+
const refMatch = normalizedOptions.find(o => o.original === val || o.raw === val)
|
|
190
|
+
if (refMatch) return refMatch.id
|
|
191
|
+
|
|
192
|
+
if (typeof val === 'object') {
|
|
193
|
+
try {
|
|
194
|
+
const str = JSON.stringify(val)
|
|
195
|
+
const structMatch = normalizedOptions.find(o =>
|
|
196
|
+
o.original && typeof o.original === 'object' && JSON.stringify(o.original) === str
|
|
197
|
+
)
|
|
198
|
+
if (structMatch) return structMatch.id
|
|
199
|
+
} catch {}
|
|
230
200
|
}
|
|
231
201
|
|
|
232
|
-
return normalizedOptions.find(o => o.userId ===
|
|
233
|
-
}, [
|
|
202
|
+
return normalizedOptions.find(o => o.userId === val)?.id ?? null
|
|
203
|
+
}, [normalizedOptions])
|
|
234
204
|
|
|
235
|
-
|
|
236
|
-
const
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
const effectiveValue = isControlled ? value : defaultValue
|
|
237
207
|
|
|
238
|
-
|
|
208
|
+
const currentSelected = normalizedOptions.find(o => o.id === selectedId)
|
|
209
|
+
const isStillValid = currentSelected && (
|
|
210
|
+
currentSelected.original === effectiveValue ||
|
|
211
|
+
currentSelected.raw === effectiveValue ||
|
|
212
|
+
currentSelected.userId === effectiveValue
|
|
213
|
+
)
|
|
239
214
|
|
|
240
|
-
|
|
215
|
+
if (!isStillValid) {
|
|
216
|
+
setSelectedId(findIdByValue(effectiveValue))
|
|
217
|
+
}
|
|
218
|
+
}, [value, defaultValue, isControlled, normalizedOptions, findIdByValue])
|
|
241
219
|
|
|
242
|
-
|
|
220
|
+
const selected = useMemo(() => {
|
|
221
|
+
return normalizedOptions.find(o => o.id === selectedId) ?? null
|
|
222
|
+
}, [selectedId, normalizedOptions])
|
|
243
223
|
|
|
244
224
|
const selectOption = useCallback((option, e) => {
|
|
245
|
-
if (option.disabled) {
|
|
246
|
-
e
|
|
247
|
-
e
|
|
225
|
+
if (option.disabled || option.loadMore) {
|
|
226
|
+
e?.stopPropagation()
|
|
227
|
+
e?.preventDefault()
|
|
228
|
+
|
|
229
|
+
if (loadingTitle !== loadMoreText) {
|
|
230
|
+
setLoadingTitle(loadMoreText)
|
|
231
|
+
loadMore()
|
|
232
|
+
}
|
|
233
|
+
|
|
248
234
|
return
|
|
249
235
|
}
|
|
250
236
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
onChange?.(option?.original, option?.userId)
|
|
254
|
-
|
|
237
|
+
setSelectedId(option.id)
|
|
238
|
+
onChange?.(option.original, option.userId)
|
|
255
239
|
setVisibility(false)
|
|
256
240
|
}, [onChange, setVisibility])
|
|
257
241
|
|
|
258
|
-
const clear = useCallback((
|
|
259
|
-
|
|
260
|
-
e.stopPropagation()
|
|
261
|
-
|
|
262
|
-
setInternalValue(null)
|
|
263
|
-
|
|
242
|
+
const clear = useCallback(() => {
|
|
243
|
+
setSelectedId(null)
|
|
264
244
|
onChange?.(null, null)
|
|
265
245
|
}, [onChange])
|
|
266
246
|
|
|
267
|
-
return {
|
|
268
|
-
|
|
247
|
+
return {
|
|
248
|
+
normalizedOptions, selected, selectOption, clear,
|
|
249
|
+
hasOptions: normalizedOptions.length > 0,
|
|
250
|
+
active: !error && !loading && !disabled && normalizedOptions.length > 0,
|
|
251
|
+
selectedValue: value ?? defaultValue,
|
|
252
|
+
placeholder, emptyText, disabledText, loadingText, errorText,
|
|
253
|
+
disabledOption, emptyOption, invalidOption, disabled, loading, error
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default useSelectLogic
|