react-animated-select 0.5.6 → 0.6.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.
@@ -1,415 +0,0 @@
1
- import {useState, useMemo, useCallback, useId, useEffect, useRef} from 'react'
2
-
3
- const SYSTEM_KEYS = ['group', 'disabled', 'options', 'items', 'children']
4
- const LABEL_KEYS = ['name', 'label', 'id', 'value']
5
- const EMPTY_ARRAY = []
6
-
7
- const getLabel = (obj, isGroup = false) => {
8
- if (isGroup && typeof obj.group === 'string') return obj.group
9
- const foundKey = LABEL_KEYS.find(k => obj[k] != null && obj[k] !== '')
10
- if (foundKey) return String(obj[foundKey])
11
-
12
- const fallback = Object.entries(obj).find(([k, v]) => !SYSTEM_KEYS.includes(k) && v != null && v !== '')
13
- return fallback ? String(fallback[1]) : null
14
- }
15
-
16
- function useSelectLogic({
17
- options = EMPTY_ARRAY,
18
- jsxOptions = EMPTY_ARRAY,
19
- value,
20
- defaultValue,
21
- onChange,
22
- disabled = false,
23
- loading = false,
24
- error = false,
25
- multiple = false,
26
- placeholder = 'Choose option',
27
- emptyText = 'No options',
28
- disabledText = 'Disabled',
29
- loadingText = 'Loading',
30
- errorText = 'Failed to load',
31
- disabledOption = 'Disabled option',
32
- emptyOption = 'Empty option',
33
- invalidOption = 'Invalid option',
34
- setVisibility,
35
- hasMore,
36
- loadButton,
37
- setLoadingTitle,
38
- loadingTitle,
39
- loadMoreText,
40
- loadMore,
41
- childrenFirst,
42
- groupsClosed
43
- }) {
44
- const stableId = useId()
45
- const isControlled = value !== undefined
46
- const [selectedId, setSelectedId] = useState(null)
47
- const [selectedIDs, setSelectedIds] = useState([])
48
- const [expandedGroups, setExpandedGroups] = useState(new Set())
49
-
50
- const orderCache = useRef(null)
51
-
52
- const toggleGroup = useCallback((groupName) => {
53
- setExpandedGroups(prev => {
54
- const next = new Set(prev)
55
- next.has(groupName) ? next.delete(groupName) : next.add(groupName)
56
- return next
57
- })
58
- }, [])
59
-
60
- const normalize = useCallback((rawItem, index, prefix = 'n', group = null, groupDisabled = false) => {
61
- const id = `${stableId}-${prefix}-${index}`
62
-
63
- if (rawItem == null || rawItem === '') {
64
- return {id, userId: null, name: emptyOption, raw: null, disabled: true, type: 'normal', group, groupDisabled}
65
- }
66
-
67
- if (typeof rawItem === 'function') {
68
- return {id, userId: null, name: invalidOption, raw: rawItem, disabled: true, invalid: true, type: 'normal', group}
69
- }
70
-
71
- if (typeof rawItem === 'object' && !Array.isArray(rawItem)) {
72
- const currentGroup = group || rawItem.group || null
73
- const isItemDisabled = groupDisabled || rawItem.disabled === true
74
- const userId = rawItem.id ?? rawItem.value ?? rawItem.name ?? rawItem.label
75
- const itemValue = rawItem.value !== undefined ? rawItem.value : (rawItem.id !== undefined ? rawItem.id : rawItem)
76
-
77
- let label = getLabel(rawItem) || (isItemDisabled ? disabledOption : emptyOption)
78
-
79
- return {
80
- id,
81
- userId,
82
- name: label,
83
- raw: itemValue,
84
- original: rawItem,
85
- disabled: isItemDisabled || (label === emptyOption && !isItemDisabled),
86
- type: typeof itemValue === 'boolean' ? 'boolean' : 'normal',
87
- group: currentGroup,
88
- groupDisabled
89
- }
90
- }
91
-
92
- return {
93
- id,
94
- userId: rawItem,
95
- name: String(rawItem),
96
- raw: rawItem,
97
- original: rawItem,
98
- disabled: groupDisabled,
99
- type: typeof rawItem === 'boolean' ? 'boolean' : 'normal',
100
- group
101
- }
102
- }, [stableId, emptyOption, invalidOption, disabledOption])
103
-
104
- const normalizedOptions = useMemo(() => {
105
- const groupsMap = new Map()
106
- const flatBase = []
107
-
108
- const preparedJsx = jsxOptions.map((opt, index) => {
109
- if (opt.isGroupMarker) return {...opt, type: 'group-marker'}
110
- const isActuallyEmpty =
111
- !opt.label &&
112
- !opt.userId &&
113
- !opt.value &&
114
- (opt.value === undefined || opt.value === null || opt.value === '') &&
115
- !opt.hasJsx
116
- return {
117
- ...opt,
118
- id: `jsx-${opt.id}`,
119
- index: index,
120
- userId: opt.userId,
121
- raw: opt.value,
122
- original: opt.value,
123
- name: isActuallyEmpty ? emptyOption : (opt.label || opt.userId || String(opt.value || '')),
124
- disabled: !!opt.disabled || isActuallyEmpty,
125
- type: typeof opt.value === 'boolean' ? 'boolean' : 'normal',
126
- group: opt.group || null
127
- }
128
- })
129
-
130
- let flatIndex = 0
131
-
132
- const collect = (items, parentGroup = null, parentDisabled = false, depth = '0') => {
133
- if (!Array.isArray(items)) items = [items]
134
-
135
- items.forEach((item, i) => {
136
- if (!item) return
137
- const currentId = `${depth}-${i}`
138
- const isObj = typeof item === 'object' && !Array.isArray(item)
139
-
140
- const isGroup = isObj && ('options' in item || ('group' in item && !LABEL_KEYS.some(k => k in item)))
141
-
142
- if (isGroup) {
143
- const groupName = getLabel(item, true) || 'Empty group'
144
- if (!groupsMap.has(groupName)) {
145
- groupsMap.set(groupName, {disabled: !!item.disabled, closedByDefault: !!item.disabled || groupsClosed, items: []})
146
- }
147
- if (item.options) {
148
- collect(item.options, groupName, parentDisabled || !!item.disabled, currentId)
149
- } else {
150
- flatBase.push({id: `empty-${groupName}-${currentId}`, name: groupName, group: groupName, isPlaceholder: true, type: 'group-marker',index: flatIndex++})
151
- }
152
- } else if (isObj && !LABEL_KEYS.some(k => k in item) && !item.group) {
153
- Object.entries(item).forEach(([k, v], j) => {
154
- const norm = normalize(v, `${currentId}-${j}`, 'normal', parentGroup, parentDisabled)
155
- flatBase.push({ ...norm, index: flatIndex++ })
156
- })
157
- } else {
158
- const norm = normalize(item, currentId, 'normal', parentGroup, parentDisabled);
159
- flatBase.push({ ...norm, index: flatIndex++ })
160
- }
161
- })
162
- }
163
-
164
- collect(options)
165
-
166
- const combined = childrenFirst
167
- ? [...preparedJsx, ...flatBase]
168
- : [...flatBase, ...preparedJsx]
169
-
170
- if (!orderCache.current) {
171
- orderCache.current = new Map(combined.map((item, i) => [item.id, i]))
172
- } else {
173
- let hasNewItems = false;
174
- combined.forEach(item => {
175
- if (!orderCache.current.has(item.id)) hasNewItems = true;
176
- })
177
-
178
- if (hasNewItems) {
179
- const newMap = new Map()
180
- combined.forEach((item, index) => {
181
- newMap.set(item.id, index)
182
- })
183
- orderCache.current = newMap
184
- }
185
- }
186
-
187
- const orderedList = [...combined].sort((a, b) => {
188
- const indexA = orderCache.current.get(a.id) ?? 999999
189
- const indexB = orderCache.current.get(b.id) ?? 999999
190
- return indexA - indexB
191
- })
192
-
193
- const structure = []
194
- const seenGroups = new Set()
195
-
196
- orderedList.forEach(opt => {
197
- if (!opt.group) {
198
- structure.push({type: 'item', data: opt})
199
- } else {
200
- if (!seenGroups.has(opt.group)) {
201
- seenGroups.add(opt.group)
202
- structure.push({type: 'group', name: opt.group})
203
- }
204
- if (!opt.isPlaceholder && !opt.isGroupMarker) {
205
- const groupStore = groupsMap.get(opt.group) || {items: []}
206
- if (!groupsMap.has(opt.group)) groupsMap.set(opt.group, groupStore)
207
- groupStore.items.push(opt)
208
- }
209
- }
210
- })
211
-
212
- const final = []
213
- structure.forEach((entry) => {
214
- if (entry.type === 'item') {
215
- final.push(entry.data)
216
- } else {
217
- const groupName = entry.name
218
- const meta = groupsMap.get(groupName)
219
- const expanded = expandedGroups.has(groupName)
220
-
221
- final.push({
222
- id: `group-header-${groupName}`,
223
- name: groupName,
224
- disabled: !!meta?.disabled,
225
- groupHeader: true,
226
- expanded,
227
- type: 'group',
228
- hidden: false
229
- })
230
-
231
- meta?.items.forEach(item => {
232
- const hidden = expandedGroups.size > 0
233
- ? !expanded
234
- : !!groupsClosed
235
-
236
- final.push({...item, hidden: hidden})
237
- })
238
- }
239
- })
240
-
241
- if (hasMore && loadButton) {
242
- final.push({
243
- id: 'special-load-more-id',
244
- name: loadingTitle,
245
- loadMore: true,
246
- loading: loadingTitle === loadMoreText,
247
- type: 'special'
248
- })
249
- }
250
-
251
- return final
252
- }, [options, jsxOptions, stableId, normalize, childrenFirst, hasMore, loadButton, loadingTitle, loadMoreText, groupsClosed, expandedGroups, emptyOption])
253
-
254
- useEffect(() => {
255
- if (!normalizedOptions || normalizedOptions.length === 0) return
256
- if (expandedGroups.size > 0) return
257
- if (groupsClosed) return
258
- const initial = new Set()
259
- normalizedOptions.forEach(opt => {
260
- if (opt.groupHeader && !opt.disabled) {
261
- initial.add(opt.name)
262
- }
263
- })
264
- if (initial.size > 0) setExpandedGroups(initial)
265
- }, [normalizedOptions, groupsClosed])
266
-
267
- const findIdByValue = useCallback((val) => {
268
- if (val == null) return null
269
- const match = normalizedOptions.find(o => o.original === val)
270
- if (match) return match.id
271
-
272
- if (typeof val === 'object') {
273
- try {
274
- const str = JSON.stringify(val)
275
- return normalizedOptions.find(o =>
276
- o.original && typeof o.original === 'object' && JSON.stringify(o.original) === str
277
- )?.id ?? null
278
- } catch { return null }
279
- }
280
- return null
281
- }, [normalizedOptions])
282
-
283
- useEffect(() => {
284
- const effectiveValue = isControlled ? value : defaultValue
285
-
286
- if (effectiveValue == null || (Array.isArray(effectiveValue) && effectiveValue.length === 0)) {
287
- setSelectedId(null)
288
- setSelectedIds([])
289
- return
290
- }
291
-
292
- const getOrVirtualize = (val) => {
293
- const id = findIdByValue(val)
294
- const found = normalizedOptions.find(o => o.id === id)
295
- if (found) return found
296
-
297
- const stableKey = typeof val === 'object' ? (val.id || val.value || JSON.stringify(val)) : String(val)
298
-
299
- if (typeof val === 'object' && val !== null) {
300
- return {
301
- id: `virtual-${stableKey}`,
302
- name: getLabel(val) || String(val.id || 'Selected Object'),
303
- raw: val.value ?? val.id ?? val,
304
- original: val,
305
- userId: val.id ?? val.value ?? null,
306
- virtual: true
307
- }
308
- }
309
- return {
310
- id: `virtual-${stableKey}`,
311
- name: String(val),
312
- raw: val,
313
- original: val,
314
- userId: val,
315
- virtual: true
316
- }
317
- }
318
-
319
- if (multiple) {
320
- const vals = Array.isArray(effectiveValue) ? effectiveValue : [effectiveValue]
321
- const newSelected = vals.map(getOrVirtualize)
322
-
323
- setSelectedIds(newSelected)
324
- } else {
325
- const val = Array.isArray(effectiveValue) ? effectiveValue[0] : effectiveValue
326
- const opt = getOrVirtualize(val)
327
-
328
- setSelectedId(prevId => prevId === opt.id ? prevId : opt.id)
329
- }
330
- }, [])
331
-
332
- const selected = useMemo(() => {
333
- const found = normalizedOptions.find(o => o.id === selectedId)
334
- if (found) return found
335
-
336
- if (!multiple && selectedId?.startsWith('virtual-')) {
337
- const effectiveValue = isControlled ? value : defaultValue
338
- const val = Array.isArray(effectiveValue) ? effectiveValue[0] : effectiveValue
339
- if (val) {
340
- return {
341
- id: selectedId,
342
- name: typeof val === 'object' ? getLabel(val) : String(val),
343
- original: val
344
- }
345
- }
346
- }
347
- return null
348
- }, [selectedId, normalizedOptions, multiple, isControlled, value, defaultValue])
349
-
350
- const selectOption = useCallback((option, e) => {
351
- if (option.groupHeader) {
352
- e?.stopPropagation()
353
- e?.preventDefault()
354
- if (!option.disabled) toggleGroup(option.name)
355
- return
356
- }
357
-
358
- if (option.disabled || option.loadMore) {
359
- e?.stopPropagation()
360
- e?.preventDefault()
361
- if (option.loadMore && !option.loading) {
362
- setLoadingTitle(loadMoreText)
363
- loadMore()
364
- }
365
- return
366
- }
367
-
368
- if (multiple) {
369
- if (option.disabled || option.groupHeader || option.loadMore) {
370
- e?.stopPropagation()
371
- e?.preventDefault()
372
- return
373
- }
374
-
375
- e?.stopPropagation()
376
- e?.preventDefault()
377
-
378
- const isSelected = selectedIDs?.some(item => item.id === option.id)
379
- const next = isSelected
380
- ? selectedIDs.filter(item => item.id !== option.id)
381
- : [...selectedIDs, option]
382
-
383
- setSelectedIds(next)
384
- onChange?.(next.map(o => o.original), next.map(o => o.userId))
385
- return
386
- }
387
- setSelectedId(option.id)
388
- onChange?.(option.original, option.userId)
389
- setVisibility(false)
390
- }, [onChange, setVisibility, loadMore, loadMoreText, setLoadingTitle, toggleGroup])
391
-
392
- const clear = useCallback(() => {
393
- setSelectedId(null)
394
- setSelectedIds([])
395
- onChange?.(null, null)
396
- }, [onChange])
397
-
398
- const removeOption = useCallback((id) => {
399
- const next = selectedIDs.filter(item => item.id !== id)
400
- setSelectedIds(next)
401
- onChange?.(next.map(o => o.original), next.map(o => o.userId))
402
- }, [selectedIDs, onChange])
403
-
404
- return {
405
- normalizedOptions, selected, selectOption, clear, removeOption,
406
- hasOptions: normalizedOptions.length > 0,
407
- active: !error && !loading && !disabled && normalizedOptions.length > 0,
408
- selectedValue: value ?? defaultValue,
409
- placeholder, emptyText, disabledText, loadingText, errorText,
410
- disabledOption, emptyOption, invalidOption, disabled, loading, error,
411
- expandedGroups, toggleGroup, selectedIDs, multiple, setSelectedIds
412
- }
413
- }
414
-
415
- export default useSelectLogic
package/vite.config.js DELETED
@@ -1,27 +0,0 @@
1
- import {defineConfig} from 'vite'
2
- import react from '@vitejs/plugin-react'
3
- import {libInjectCss} from 'vite-plugin-lib-inject-css'
4
-
5
- export default defineConfig({
6
- plugins: [
7
- react(),
8
- libInjectCss()
9
- ],
10
- build: {
11
- lib: {
12
- entry: 'src/index.js',
13
- name: 'ReactAnimatedSelect',
14
- formats: ['es', 'cjs'],
15
- fileName: (format) => `index.${format}.js`,
16
- },
17
- rollupOptions: {
18
- external: ['react', 'react-dom', 'react-transition-group'],
19
- output: {
20
- exports: 'named',
21
- globals: {
22
- react: 'React', 'react-dom': 'ReactDOM' ,'react-transition-group': 'ReactTransitionGroup'
23
- },
24
- },
25
- },
26
- },
27
- })