react-animated-select 0.2.8 → 0.2.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-animated-select",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "author": "l1nway (https://github.com/l1nway/)",
6
6
  "description": "Animated, accessible, and flexible select component for React",
package/src/select.jsx CHANGED
@@ -179,6 +179,8 @@ const Select = forwardRef(({
179
179
  if (index === highlightedIndex) optionClass += ' rac-highlighted'
180
180
 
181
181
  if (element.disabled) optionClass += ' rac-disabled-option'
182
+
183
+ if (element.isInvalid) optionClass += ' rac-invalid-option'
182
184
 
183
185
  if (typeof element.raw === 'boolean') {
184
186
  optionClass += element.raw ? ' rac-true-option' : ' rac-false-option'
@@ -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
  export 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',
@@ -22,247 +19,200 @@ export function useSelectLogic({
22
19
  invalidOption = 'Invalid option',
23
20
  setVisibility
24
21
  }) {
25
-
26
22
  const stableId = useId()
27
-
28
- // controlled select check
29
23
  const isControlled = value !== undefined
30
-
31
- // filled in value of the select by the user via prop
32
- const [internalValue, setInternalValue] = useState(defaultValue)
33
-
34
- // value chosenness status
35
- const selectedValue = isControlled ? value : internalValue
24
+
25
+ const [selectedId, setSelectedId] = useState(null)
36
26
 
37
27
  const isOptionObject = (obj) => {
38
28
  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
- )
29
+ return ('id' in obj || 'value' in obj || 'name' in obj || 'label' in obj || 'disabled' in obj)
46
30
  }
47
31
 
48
- // normalization list of options
49
- const normalizedPropOptions = useMemo(() => {
50
- if (!options) return []
51
-
32
+ const normalizedOptions = useMemo(() => {
52
33
  const flat = []
53
-
54
- const push = (key, value, originalItem) => {
55
- let computedUserId = originalItem?.id ?? originalItem?.value
56
- if (computedUserId === undefined || computedUserId === null) {
57
- computedUserId = value ?? key
58
- }
59
-
60
- if (typeof value === 'number' && value === 0) {
61
- flat.push({
62
- key: '0',
63
- value: 0,
64
- userId: 0,
65
- label: '0',
66
- original: originalItem
34
+ const push = (key, val, originalItem) => {
35
+ let computedUserId = originalItem?.id ?? originalItem?.value ?? val ?? key
36
+
37
+ if (typeof val === 'function') {
38
+ flat.push({
39
+ key: `invalid-${flat.length}`,
40
+ value: val,
41
+ userId: null,
42
+ disabled: true,
43
+ isInvalid: true,
44
+ label: invalidOption,
45
+ original: originalItem
67
46
  })
68
47
  return
69
48
  }
70
49
 
71
- if (typeof value == 'boolean') {
72
- flat.push({
73
- key: `bool-${flat.length}`,
74
- value,
75
- userId: computedUserId,
76
- label: String(value),
77
- type: 'boolean',
78
- original: originalItem
50
+ if (val === '') {
51
+ flat.push({
52
+ key: `empty-str-${flat.length}`,
53
+ value: '',
54
+ userId: null,
55
+ disabled: true,
56
+ label: emptyOption,
57
+ original: originalItem
79
58
  })
80
59
  return
81
60
  }
82
61
 
83
- if (value === '' || value === null || value === undefined) {
84
- flat.push({
85
- key: `empty-${flat.length}`,
86
- value: null,
87
- userId: computedUserId ?? `empty-${flat.length}`,
88
- disabled: true,
89
- label: emptyOption,
90
- original: originalItem
62
+ if (val === null || val === undefined) {
63
+ flat.push({
64
+ key: `empty-${flat.length}`,
65
+ value: null,
66
+ userId: null,
67
+ disabled: true,
68
+ label: emptyOption,
69
+ original: originalItem
91
70
  })
92
71
  return
93
72
  }
94
73
 
95
- if (typeof value == 'function') {
96
- flat.push({
97
- key: `invalid-${flat.length}`,
98
- value: null,
99
- userId: computedUserId ?? `invalid-${flat.length}`,
100
- disabled: true,
101
- label: invalidOption,
102
- original: originalItem
74
+ if (typeof val === 'number' || typeof val === 'boolean') {
75
+ flat.push({
76
+ key: `${typeof val}-${val}-${flat.length}`,
77
+ value: val,
78
+ userId: computedUserId,
79
+ label: String(val),
80
+ original: originalItem
103
81
  })
104
82
  return
105
83
  }
106
84
 
107
- if (value && typeof value === 'object' && !Array.isArray(value)) {
85
+ if (val && typeof val === 'object' && !Array.isArray(val)) {
108
86
  flat.push({
109
- key: value.id ?? value.value ?? value.name ?? key,
110
- value,
87
+ key: val.id ?? val.value ?? val.name ?? key ?? `obj-${flat.length}`,
88
+ value: val,
111
89
  userId: computedUserId,
112
- disabled: !!value.disabled,
113
- label: value.name ?? value.label ?? key,
90
+ disabled: !!val.disabled,
91
+ label: val.name ?? val.label ?? String(key),
114
92
  original: originalItem
115
93
  })
116
94
  } else {
117
- flat.push({
118
- key,
119
- value,
120
- userId: computedUserId,
121
- label: String(value ?? key),
122
- original: originalItem
95
+ flat.push({
96
+ key: key ?? `opt-${flat.length}`,
97
+ value: val,
98
+ userId: computedUserId,
99
+ label: String(val ?? key),
100
+ original: originalItem
123
101
  })
124
102
  }
125
103
  }
126
104
 
127
105
  if (Array.isArray(options)) {
128
- for (const item of options) {
129
- if (
130
- item &&
131
- typeof item == 'object' &&
132
- !Array.isArray(item) &&
133
- Object.keys(item).length == 1 &&
134
- item.disabled == true
135
- ) {
106
+ options.forEach((item, index) => {
107
+ if (item && typeof item === 'object' && Object.keys(item).length === 1 && item.disabled === true) {
108
+ flat.push({ key: `dis-${index}`, value: null, userId: null, disabled: true, label: disabledOption, original: item })
109
+ } else if (isOptionObject(item)) {
110
+ const stableUserId = item.id ?? (typeof item.value !== 'object' ? item.value : (item.label ?? item.name ?? item.value));
136
111
  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
- }
146
-
147
- if (isOptionObject(item)) {
148
-
149
- const stableUserId = item.id ??
150
- (typeof item.value !== 'object' ? item.value : (item.label ?? item.name ?? item.value));
151
-
152
- flat.push({
153
- key: item.id ?? item.value ?? item.name ?? `opt-${flat.length}`,
154
- value: item.value ?? item.id ?? item,
112
+ key: item.id ?? item.value ?? item.name ?? `opt-${index}`,
113
+ value: item.value !== undefined ? item.value : (item.id !== undefined ? item.id : item),
155
114
  userId: stableUserId,
156
115
  disabled: !!item.disabled,
157
116
  label: item.name ?? item.label ?? String(item.id ?? item.value),
158
117
  original: item
159
118
  })
160
- }
161
- else if (item && typeof item == 'object') {
162
- for (const [k, v] of Object.entries(item)) {
163
- push(k, v, v)
164
- }
165
- }
166
- else {
119
+ } else if (item && typeof item === 'object' && !Array.isArray(item)) {
120
+ Object.entries(item).forEach(([k, v]) => push(k, v, v))
121
+ } else {
167
122
  push(item, item, item)
168
123
  }
169
- }
170
- }
171
- else if (typeof options == 'object') {
172
- for (const [k, v] of Object.entries(options)) {
173
- push(k, v, v)
174
- }
124
+ })
125
+ } else if (typeof options === 'object' && options !== null) {
126
+ Object.entries(options).forEach(([k, v]) => push(k, v, v))
175
127
  }
176
128
 
177
- return flat.map((item, i) => {
178
- const internalId = `${stableId}-opt-${i}`
179
-
180
- return {
181
- id: internalId,
182
- userId: item.userId,
183
- name: String(item.label),
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,
201
- raw: opt.value,
202
- original: opt.value,
203
- name: opt.label,
204
- jsx: opt.jsx,
205
- disabled: opt.disabled,
206
- className: opt.className,
207
- type: typeof opt.value === 'boolean' ? 'boolean' : 'normal'
208
- }
209
- })
210
- }, [jsxOptions, stableId])
211
-
212
- const normalizedOptions = useMemo(() => {
213
- return [...normalizedPropOptions, ...normalizedJsxOptions]
214
- }, [normalizedPropOptions, normalizedJsxOptions])
215
-
216
- const hasOptions = normalizedOptions.length > 0
217
-
218
- const active = useMemo(() => (
219
- !error && !loading && !disabled && hasOptions
220
- ), [error, loading, disabled, hasOptions])
221
-
222
- const controlledId = useMemo(() => {
223
- if (!isControlled) return null
129
+ const propOpts = flat.map((item, i) => ({
130
+ id: `${stableId}-opt-${i}`,
131
+ userId: item.userId,
132
+ name: String(item.label),
133
+ raw: item.value,
134
+ original: item.original,
135
+ disabled: item.disabled,
136
+ isInvalid: item.isInvalid,
137
+ type: typeof item.value === 'boolean' ? 'boolean' : 'normal'
138
+ }))
139
+
140
+ const jsxOpts = jsxOptions.map((opt, index) => ({
141
+ ...opt,
142
+ id: `jsx-${stableId.replace(/:/g, '')}-${opt.id}-${index}`,
143
+ userId: opt.id,
144
+ raw: opt.value,
145
+ original: opt.value,
146
+ name: opt.label,
147
+ type: typeof opt.value === 'boolean' ? 'boolean' : 'normal'
148
+ }))
149
+
150
+ return [...propOpts, ...jsxOpts]
151
+ }, [options, jsxOptions, stableId, emptyOption, disabledOption])
152
+
153
+ const findIdByValue = useCallback((val) => {
154
+ if (val === undefined || val === null) return null
224
155
 
225
- if (internalValue) {
226
- const currentCachedOption = normalizedOptions.find(o => o.id === internalValue)
227
- if (currentCachedOption && currentCachedOption.userId === value) {
228
- return internalValue
229
- }
156
+ const refMatch = normalizedOptions.find(o => o.original === val || o.raw === val)
157
+ if (refMatch) return refMatch.id
158
+
159
+ if (typeof val === 'object') {
160
+ try {
161
+ const str = JSON.stringify(val)
162
+ const structMatch = normalizedOptions.find(o =>
163
+ o.original && typeof o.original === 'object' && JSON.stringify(o.original) === str
164
+ )
165
+ if (structMatch) return structMatch.id
166
+ } catch {}
230
167
  }
231
168
 
232
- return normalizedOptions.find(o => o.userId === value)?.id ?? null
233
- }, [isControlled, value, normalizedOptions, internalValue])
169
+ return normalizedOptions.find(o => o.userId === val)?.id ?? null
170
+ }, [normalizedOptions])
234
171
 
235
- const selected = useMemo(() => {
236
- const currentId = isControlled ? controlledId : internalValue
172
+ useEffect(() => {
173
+ const effectiveValue = isControlled ? value : defaultValue
237
174
 
238
- if (!currentId) return null
175
+ const currentSelected = normalizedOptions.find(o => o.id === selectedId)
176
+ const isStillValid = currentSelected && (
177
+ currentSelected.original === effectiveValue ||
178
+ currentSelected.raw === effectiveValue ||
179
+ currentSelected.userId === effectiveValue
180
+ )
239
181
 
240
- return normalizedOptions.find(o => o.id === currentId) ?? null
182
+ if (!isStillValid) {
183
+ setSelectedId(findIdByValue(effectiveValue))
184
+ }
185
+ }, [value, defaultValue, isControlled, normalizedOptions, findIdByValue])
241
186
 
242
- }, [isControlled, controlledId, internalValue, normalizedOptions])
187
+ const selected = useMemo(() => {
188
+ return normalizedOptions.find(o => o.id === selectedId) ?? null
189
+ }, [selectedId, normalizedOptions])
243
190
 
244
191
  const selectOption = useCallback((option, e) => {
245
192
  if (option.disabled) {
246
- e.stopPropagation()
247
- e.preventDefault()
193
+ e?.stopPropagation()
194
+ e?.preventDefault()
248
195
  return
249
196
  }
250
197
 
251
- setInternalValue(option.id)
252
-
253
- onChange?.(option?.original, option?.userId)
254
-
198
+ setSelectedId(option.id)
199
+ onChange?.(option.original, option.userId)
255
200
  setVisibility(false)
256
201
  }, [onChange, setVisibility])
257
202
 
258
203
  const clear = useCallback((e) => {
259
204
  e.preventDefault()
260
205
  e.stopPropagation()
261
-
262
- setInternalValue(null)
263
-
206
+ setSelectedId(null)
264
207
  onChange?.(null, null)
265
208
  }, [onChange])
266
209
 
267
- return {normalizedOptions, selected, selectOption, clear, hasOptions, active, selectedValue, placeholder, emptyText, disabledText, loadingText, errorText, disabledOption, emptyOption, invalidOption, disabled, loading, error}
210
+ return {
211
+ normalizedOptions, selected, selectOption, clear,
212
+ hasOptions: normalizedOptions.length > 0,
213
+ active: !error && !loading && !disabled && normalizedOptions.length > 0,
214
+ selectedValue: value ?? defaultValue,
215
+ placeholder, emptyText, disabledText, loadingText, errorText,
216
+ disabledOption, emptyOption, invalidOption, disabled, loading, error
217
+ }
268
218
  }