react-animated-select 0.3.1 → 0.3.6
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 +31 -49
- package/demo/package-lock.json +5 -5
- package/demo/package.json +3 -2
- package/demo/src/App.tsx +56 -43
- package/demo/src/rac.css +6 -25
- package/dist/index.cjs.js +6 -6
- package/dist/index.css +1 -1
- package/dist/index.es.js +911 -769
- package/index.d.ts +10 -1
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/optgroup.jsx +36 -0
- package/src/option.jsx +29 -13
- package/src/options.jsx +11 -11
- package/src/select.css +39 -2
- package/src/select.jsx +203 -263
- package/src/selectContext.js +1 -1
- package/src/selectJSX.jsx +148 -0
- package/src/slideDown.jsx +36 -0
- package/src/useSelect.jsx +83 -118
- package/src/useSelectLogic.jsx +184 -139
package/src/useSelectLogic.jsx
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import {useState, useMemo, useCallback, useId, useEffect} from 'react'
|
|
2
2
|
|
|
3
|
+
// keys that cannot be taken as a name if labelKeys are unavailable
|
|
4
|
+
const systemKeys = ['group', 'disabled', 'options', 'items', 'children']
|
|
5
|
+
|
|
6
|
+
// main keys in order of priority that can be taken for the name
|
|
7
|
+
const labelKeys = ['name', 'label', 'id', 'value']
|
|
8
|
+
|
|
3
9
|
function useSelectLogic({
|
|
4
10
|
options = [],
|
|
5
11
|
jsxOptions = [],
|
|
@@ -28,184 +34,218 @@ function useSelectLogic({
|
|
|
28
34
|
}) {
|
|
29
35
|
const stableId = useId()
|
|
30
36
|
const isControlled = value !== undefined
|
|
31
|
-
|
|
32
37
|
const [selectedId, setSelectedId] = useState(null)
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
// getting option name
|
|
40
|
+
const getLabelFromObject = useCallback((obj, fallback = false) => {
|
|
41
|
+
//
|
|
42
|
+
const foundKey = labelKeys.find(k => obj[k] !== undefined && obj[k] !== null && obj[k] !== '')
|
|
43
|
+
if (foundKey) return String(obj[foundKey])
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
const fallbackEntry = Object.entries(obj).find(([k, v]) =>
|
|
46
|
+
!systemKeys.includes(k) && v != null && v !== ''
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if (fallbackEntry) return String(fallbackEntry[1])
|
|
50
|
+
return fallback
|
|
51
|
+
}, [])
|
|
52
|
+
|
|
53
|
+
const createNormalizedOption = useCallback((rawItem, index, type = 'normal', injectedGroup = null, injectedDisabled = false) => {
|
|
54
|
+
let label = ''
|
|
55
|
+
let itemValue = rawItem
|
|
56
|
+
let isDisabled = injectedDisabled
|
|
57
|
+
let userId = null
|
|
58
|
+
let group = injectedGroup
|
|
59
|
+
|
|
60
|
+
if (rawItem == null || rawItem === '') {
|
|
61
|
+
return {
|
|
62
|
+
id: `${stableId}-${type}-${index}`,
|
|
63
|
+
userId: null,
|
|
64
|
+
name: emptyOption,
|
|
65
|
+
raw: null,
|
|
66
|
+
disabled: true,
|
|
67
|
+
type: 'normal',
|
|
68
|
+
group: group
|
|
55
69
|
}
|
|
70
|
+
}
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
if (typeof rawItem === 'function') {
|
|
73
|
+
return {
|
|
74
|
+
id: `${stableId}-inv-${index}`,
|
|
75
|
+
userId: null,
|
|
76
|
+
name: invalidOption,
|
|
77
|
+
raw: rawItem,
|
|
78
|
+
disabled: true,
|
|
79
|
+
invalid: true,
|
|
80
|
+
type: 'normal',
|
|
81
|
+
group: group
|
|
67
82
|
}
|
|
83
|
+
}
|
|
68
84
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
if (typeof rawItem === 'object' && !Array.isArray(rawItem)) {
|
|
86
|
+
if (!group) group = rawItem.group || null
|
|
87
|
+
isDisabled = isDisabled || !!rawItem.disabled
|
|
88
|
+
|
|
89
|
+
userId = rawItem.id ?? rawItem.value ?? rawItem.name ?? rawItem.label
|
|
90
|
+
itemValue = rawItem.value !== undefined ? rawItem.value : (rawItem.id !== undefined ? rawItem.id : rawItem)
|
|
91
|
+
|
|
92
|
+
label = getLabelFromObject(rawItem, isDisabled ? disabledOption : emptyOption)
|
|
93
|
+
if (label === emptyOption && !isDisabled) isDisabled = true
|
|
94
|
+
} else {
|
|
95
|
+
label = String(rawItem)
|
|
96
|
+
userId = rawItem
|
|
97
|
+
itemValue = rawItem
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
id: `${stableId}-${type}-${index}`,
|
|
102
|
+
userId: userId,
|
|
103
|
+
name: label,
|
|
104
|
+
raw: itemValue,
|
|
105
|
+
original: rawItem,
|
|
106
|
+
disabled: isDisabled,
|
|
107
|
+
type: typeof itemValue === 'boolean' ? 'boolean' : 'normal',
|
|
108
|
+
group: group
|
|
109
|
+
}
|
|
110
|
+
}, [stableId, emptyOption, invalidOption, disabledOption, getLabelFromObject])
|
|
111
|
+
|
|
112
|
+
const [expandedGroups, setExpandedGroups] = useState(new Set())
|
|
113
|
+
|
|
114
|
+
const toggleGroup = useCallback((groupName) => {
|
|
115
|
+
setExpandedGroups(prev => {
|
|
116
|
+
const next = new Set(prev)
|
|
117
|
+
if (next.has(groupName)) next.delete(groupName)
|
|
118
|
+
else next.add(groupName)
|
|
119
|
+
return next
|
|
120
|
+
})
|
|
121
|
+
}, [])
|
|
122
|
+
|
|
123
|
+
const normalizedOptions = useMemo(() => {
|
|
124
|
+
const combined = []
|
|
125
|
+
|
|
126
|
+
const processItem = (opt, uniqueIdx, parentGroup = null, parentDisabled = false) => {
|
|
127
|
+
if (opt && typeof opt === 'object' && !Array.isArray(opt) && 'options' in opt) {
|
|
128
|
+
const groupName = getLabelFromObject(opt, 'Empty group', true)
|
|
129
|
+
const isGroupDisabled = parentDisabled || !!opt.disabled
|
|
130
|
+
const innerData = opt.options
|
|
131
|
+
|
|
132
|
+
if (Array.isArray(innerData)) {
|
|
133
|
+
innerData.forEach((child, i) => processItem(child, `${uniqueIdx}-${i}`, groupName, isGroupDisabled))
|
|
134
|
+
} else if (innerData && typeof innerData === 'object') {
|
|
135
|
+
Object.entries(innerData).forEach(([k, v], i) => processItem(v, `${uniqueIdx}-${i}`, groupName, isGroupDisabled))
|
|
136
|
+
} else {
|
|
137
|
+
processItem(innerData, `${uniqueIdx}-0`, groupName, isGroupDisabled)
|
|
138
|
+
}
|
|
78
139
|
return
|
|
79
140
|
}
|
|
80
141
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
original: originalItem
|
|
142
|
+
const isMapObj = opt && typeof opt === 'object' && !Array.isArray(opt) &&
|
|
143
|
+
!labelKeys.some(k => k in opt) && !('group' in opt)
|
|
144
|
+
|
|
145
|
+
if (isMapObj) {
|
|
146
|
+
Object.entries(opt).forEach(([k, v], j) => {
|
|
147
|
+
combined.push(createNormalizedOption(v, `${uniqueIdx}-${j}`, 'normal', parentGroup, parentDisabled))
|
|
88
148
|
})
|
|
89
149
|
return
|
|
90
|
-
} else {
|
|
91
|
-
flat.push({
|
|
92
|
-
key: key ?? `opt-${flat.length}`,
|
|
93
|
-
value: val,
|
|
94
|
-
userId: computedUserId,
|
|
95
|
-
label: String(val ?? key),
|
|
96
|
-
original: originalItem
|
|
97
|
-
})
|
|
98
150
|
}
|
|
151
|
+
|
|
152
|
+
combined.push(createNormalizedOption(opt, uniqueIdx, 'normal', parentGroup, parentDisabled))
|
|
99
153
|
}
|
|
100
154
|
|
|
101
155
|
if (Array.isArray(options)) {
|
|
102
|
-
options.forEach((
|
|
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))
|
|
107
|
-
|
|
108
|
-
let rawLabel = item.name || item.label || item.id || item.value
|
|
109
|
-
|
|
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)
|
|
121
|
-
|
|
122
|
-
flat.push({
|
|
123
|
-
key: item.id ?? item.value ?? item.name ?? `opt-${index}`,
|
|
124
|
-
value: item.value !== undefined ? item.value : (item.id !== undefined ? item.id : item),
|
|
125
|
-
userId: stableUserId,
|
|
126
|
-
disabled: hasNoContent || !!item.disabled,
|
|
127
|
-
label: finalLabel,
|
|
128
|
-
original: item
|
|
129
|
-
})
|
|
130
|
-
} else if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
131
|
-
Object.entries(item).forEach(([k, v]) => push(k, v, v))
|
|
132
|
-
} else {
|
|
133
|
-
push(item, item, item)
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
} else if (typeof options === 'object' && options !== null) {
|
|
137
|
-
Object.entries(options).forEach(([k, v]) => push(k, v, v))
|
|
156
|
+
options.forEach((opt, i) => processItem(opt, i))
|
|
138
157
|
}
|
|
139
158
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
156
|
-
|
|
159
|
+
const jsxMapped = jsxOptions.map(opt => {
|
|
160
|
+
if (opt.isGroupMarker) return { ...opt, type: 'group-marker' }
|
|
161
|
+
const isActuallyEmpty = (opt.value == null || opt.value === '') && !opt.label
|
|
157
162
|
return {
|
|
158
163
|
...opt,
|
|
159
|
-
id: `jsx-${
|
|
164
|
+
id: `jsx-${opt.id}`,
|
|
160
165
|
userId: opt.id,
|
|
161
166
|
raw: opt.value,
|
|
162
167
|
original: opt.value,
|
|
163
168
|
name: isActuallyEmpty ? emptyOption : opt.label,
|
|
164
|
-
disabled: opt.disabled || isActuallyEmpty,
|
|
165
|
-
type: typeof opt.value === 'boolean' ? 'boolean' : 'normal'
|
|
169
|
+
disabled: !!opt.disabled || isActuallyEmpty,
|
|
170
|
+
type: typeof opt.value === 'boolean' ? 'boolean' : 'normal',
|
|
171
|
+
group: opt.group || null
|
|
166
172
|
}
|
|
167
173
|
})
|
|
168
174
|
|
|
169
|
-
const
|
|
175
|
+
const baseList = childrenFirst ? [...jsxMapped, ...combined] : [...combined, ...jsxMapped]
|
|
170
176
|
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
const finalFlattened = []
|
|
178
|
+
const groupsMap = new Map()
|
|
179
|
+
const order = []
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
baseList.forEach(opt => {
|
|
182
|
+
if (!opt.group) {
|
|
183
|
+
order.push({type: 'item', data: opt})
|
|
184
|
+
} else {
|
|
185
|
+
if (!groupsMap.has(opt.group)) {
|
|
186
|
+
groupsMap.set(opt.group, [])
|
|
187
|
+
order.push({type: 'group', name: opt.group})
|
|
188
|
+
}
|
|
189
|
+
if (!opt.isGroupMarker) {
|
|
190
|
+
const visible = expandedGroups.has(opt.group)
|
|
191
|
+
groupsMap.get(opt.group).push({...opt, hidden: !visible})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
order.forEach((entry, idx) => {
|
|
197
|
+
if (entry.type === 'item') {
|
|
198
|
+
finalFlattened.push(entry.data)
|
|
199
|
+
} else {
|
|
200
|
+
const expanded = expandedGroups.has(entry.name)
|
|
201
|
+
finalFlattened.push({
|
|
202
|
+
id: `group-header-${entry.name}-${idx}`,
|
|
203
|
+
name: entry.name,
|
|
204
|
+
disabled: false,
|
|
205
|
+
groupHeader: true,
|
|
206
|
+
expanded,
|
|
207
|
+
type: 'group'
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const items = groupsMap.get(entry.name)
|
|
211
|
+
finalFlattened.push(...items)
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
if (hasMore && loadButton) {
|
|
217
|
+
finalFlattened.push({
|
|
175
218
|
id: 'special-load-more-id',
|
|
176
219
|
name: loadingTitle,
|
|
177
220
|
loadMore: true,
|
|
178
|
-
loading:
|
|
221
|
+
loading: loadingTitle === loadMoreText,
|
|
179
222
|
type: 'special'
|
|
180
223
|
})
|
|
181
224
|
}
|
|
182
225
|
|
|
183
|
-
return
|
|
184
|
-
}, [options, jsxOptions, stableId,
|
|
226
|
+
return finalFlattened
|
|
227
|
+
}, [options, jsxOptions, stableId, createNormalizedOption, childrenFirst, hasMore, loadButton, loadingTitle, loadMoreText, emptyText, emptyOption, getLabelFromObject])
|
|
185
228
|
|
|
186
229
|
const findIdByValue = useCallback((val) => {
|
|
187
|
-
if (val
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (refMatch) return refMatch.id
|
|
230
|
+
if (val == null) return null
|
|
231
|
+
const match = normalizedOptions.find(o => o.original === val || o.raw === val || o.userId === val)
|
|
232
|
+
if (match) return match.id
|
|
191
233
|
|
|
192
234
|
if (typeof val === 'object') {
|
|
193
235
|
try {
|
|
194
236
|
const str = JSON.stringify(val)
|
|
195
|
-
|
|
237
|
+
return normalizedOptions.find(o =>
|
|
196
238
|
o.original && typeof o.original === 'object' && JSON.stringify(o.original) === str
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
} catch {}
|
|
239
|
+
)?.id ?? null
|
|
240
|
+
} catch { return null }
|
|
200
241
|
}
|
|
201
|
-
|
|
202
|
-
return normalizedOptions.find(o => o.userId === val)?.id ?? null
|
|
242
|
+
return null
|
|
203
243
|
}, [normalizedOptions])
|
|
204
244
|
|
|
205
245
|
useEffect(() => {
|
|
206
246
|
const effectiveValue = isControlled ? value : defaultValue
|
|
207
|
-
|
|
208
247
|
const currentSelected = normalizedOptions.find(o => o.id === selectedId)
|
|
248
|
+
|
|
209
249
|
const isStillValid = currentSelected && (
|
|
210
250
|
currentSelected.original === effectiveValue ||
|
|
211
251
|
currentSelected.raw === effectiveValue ||
|
|
@@ -215,29 +255,33 @@ function useSelectLogic({
|
|
|
215
255
|
if (!isStillValid) {
|
|
216
256
|
setSelectedId(findIdByValue(effectiveValue))
|
|
217
257
|
}
|
|
218
|
-
}, [value, defaultValue, isControlled, normalizedOptions, findIdByValue])
|
|
258
|
+
}, [value, defaultValue, isControlled, normalizedOptions, findIdByValue, selectedId])
|
|
219
259
|
|
|
220
|
-
const selected = useMemo(() =>
|
|
221
|
-
|
|
222
|
-
|
|
260
|
+
const selected = useMemo(() =>
|
|
261
|
+
normalizedOptions.find(o => o.id === selectedId) ?? null,
|
|
262
|
+
[selectedId, normalizedOptions])
|
|
223
263
|
|
|
224
264
|
const selectOption = useCallback((option, e) => {
|
|
225
|
-
if (option.
|
|
265
|
+
if (option.groupHeader) {
|
|
226
266
|
e?.stopPropagation()
|
|
227
267
|
e?.preventDefault()
|
|
268
|
+
toggleGroup(option.name)
|
|
269
|
+
return
|
|
270
|
+
}
|
|
228
271
|
|
|
229
|
-
|
|
272
|
+
if (option.disabled || option.loadMore) {
|
|
273
|
+
e?.stopPropagation()
|
|
274
|
+
e?.preventDefault()
|
|
275
|
+
if (option.loadMore) {
|
|
230
276
|
setLoadingTitle(loadMoreText)
|
|
231
277
|
loadMore()
|
|
232
278
|
}
|
|
233
|
-
|
|
234
279
|
return
|
|
235
280
|
}
|
|
236
|
-
|
|
237
281
|
setSelectedId(option.id)
|
|
238
282
|
onChange?.(option.original, option.userId)
|
|
239
283
|
setVisibility(false)
|
|
240
|
-
}, [onChange, setVisibility])
|
|
284
|
+
}, [onChange, setVisibility, loadMore, loadMoreText, setLoadingTitle])
|
|
241
285
|
|
|
242
286
|
const clear = useCallback(() => {
|
|
243
287
|
setSelectedId(null)
|
|
@@ -250,7 +294,8 @@ function useSelectLogic({
|
|
|
250
294
|
active: !error && !loading && !disabled && normalizedOptions.length > 0,
|
|
251
295
|
selectedValue: value ?? defaultValue,
|
|
252
296
|
placeholder, emptyText, disabledText, loadingText, errorText,
|
|
253
|
-
disabledOption, emptyOption, invalidOption, disabled, loading, error
|
|
297
|
+
disabledOption, emptyOption, invalidOption, disabled, loading, error,
|
|
298
|
+
expandedGroups, toggleGroup, visibleOptions: normalizedOptions.filter(o => !o.hidden)
|
|
254
299
|
}
|
|
255
300
|
}
|
|
256
301
|
|