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