react-animated-select 0.2.9 → 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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {FC, ReactNode} from 'react'
1
+ import {FC, ReactNode, CSSProperties, ElementType} from 'react'
2
2
 
3
3
  export interface SelectProps {
4
4
 
@@ -38,6 +38,15 @@ export interface SelectProps {
38
38
  style?: CSSProperties
39
39
  ArrowIcon?: ElementType | string | ReactNode
40
40
  ClearIcon?: ElementType | string | ReactNode
41
+
42
+ hasMore?: boolean
43
+ loadMore?: () => void
44
+ loadButton?: boolean
45
+ loadButtonText?: string
46
+ loadMoreText?: string
47
+ loadOffset?: number
48
+ loadAhead?: number
49
+ childrenFirst?: boolean
41
50
  }
42
51
 
43
52
  export const Select: FC<SelectProps>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-animated-select",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "author": "l1nway (https://github.com/l1nway/)",
6
6
  "description": "Animated, accessible, and flexible select component for React",
@@ -16,7 +16,7 @@
16
16
  "url": "git+https://github.com/l1nway/react-animated-select.git"
17
17
  },
18
18
  "license": "MIT",
19
- "homepage": "https://github.com/l1nway/react-animated-select#readme",
19
+ "homepage": "https://l1nway.github.io/react-animated-select/",
20
20
  "scripts": {
21
21
  "build": "vite build",
22
22
  "dev": "vite build --watch"
@@ -40,6 +40,7 @@
40
40
  "react-transition-group": "^4.4.5"
41
41
  },
42
42
  "devDependencies": {
43
+ "@types/react": "^19.2.9",
43
44
  "@vitejs/plugin-react": "^5.1.2",
44
45
  "vite": "^7.3.1",
45
46
  "vite-plugin-lib-inject-css": "^2.2.2"
package/src/option.jsx CHANGED
@@ -10,11 +10,15 @@ export default function Option({value, id, className, children, disabled}) {
10
10
  if (!ctx) return
11
11
 
12
12
  const textFallback = getText(children)
13
+
14
+ const finalLabel = (typeof children === 'string' && children !== '')
15
+ ? children
16
+ : (textFallback || String(value ?? id ?? ''))
13
17
 
14
18
  const option = {
15
19
  id: String(id ?? makeId(String(textFallback))),
16
20
  value: value !== undefined ? value : textFallback,
17
- label: typeof children === 'string' ? children : String(value ?? id),
21
+ label: finalLabel,
18
22
  jsx: children,
19
23
  className,
20
24
  disabled: !!disabled
package/src/select.css CHANGED
@@ -56,11 +56,13 @@
56
56
  --rac-list-max-height: 250px;
57
57
 
58
58
  --rac-option-padding: 0.5em;
59
+ --rac-option-min-height: 1em;
59
60
  --rac-disabled-option-color: color-mix(in srgb, GrayText, CanvasText 20%);
60
61
  --rac-invalid-option-color: color-mix(in srgb, var(--rac-base-red), CanvasText 10%);
61
62
  --rac-true-option-color: color-mix(in srgb, var(--rac-base-green), CanvasText 10%);
62
63
  --rac-false-option-color: color-mix(in srgb, var(--rac-base-red), CanvasText 10%);
63
64
  --rac-warning-option-color: color-mix(in srgb, var(--rac-base-yellow), CanvasText 10%);
65
+ --rac-loading-option-opacity: 0.75;
64
66
 
65
67
  background: var(--rac-select-background);
66
68
  padding: var(--rac-select-padding);
@@ -197,6 +199,7 @@
197
199
  }
198
200
 
199
201
  .rac-select-option {
202
+ min-height: var(--rac-option-min-height);
200
203
  padding: var(--rac-option-padding);
201
204
  transition: background-color var(--rac-duration-fast) cubic-bezier(0.4,0,0.2,1);
202
205
  }
@@ -232,4 +235,8 @@
232
235
 
233
236
  .rac-false-option {
234
237
  color: var(--rac-false-option-color);
238
+ }
239
+
240
+ .rac-loading-option {
241
+ cursor: wait;
235
242
  }
package/src/select.jsx CHANGED
@@ -2,11 +2,11 @@ import './select.css'
2
2
 
3
3
  import {XMarkIcon, ArrowUpIcon} from './icons'
4
4
  import {forwardRef, useImperativeHandle, useRef, useMemo, useState, useEffect, useCallback, useId, isValidElement, cloneElement} from 'react'
5
-
6
5
  import {SelectContext} from './selectContext'
7
- import useSelect from './useSelect'
8
- import {useSelectLogic} from './useSelectLogic'
9
6
  import {makeId} from './makeId'
7
+
8
+ import useSelect from './useSelect'
9
+ import useSelectLogic from './useSelectLogic'
10
10
  import Options from './options'
11
11
  import SlideLeft from './slideLeft'
12
12
 
@@ -44,9 +44,23 @@ const Select = forwardRef(({
44
44
  className = '',
45
45
  ArrowIcon = ArrowUpIcon,
46
46
  ClearIcon = XMarkIcon,
47
+ hasMore = false,
48
+ loadMore = () => {},
49
+ loadButton = false,
50
+ loadButtonText = 'Load more',
51
+ loadMoreText = 'Loading',
52
+ loadOffset = 100,
53
+ loadAhead = 3,
54
+ childrenFirst = false,
47
55
  ...props
48
56
  }, ref) => {
49
57
 
58
+ const [loadingTitle, setLoadingTitle] = useState(loadButton ? loadButtonText : loadMoreText)
59
+
60
+ useEffect(() => {
61
+ setLoadingTitle(loadButton ? loadButtonText : loadMoreText)
62
+ }, [loadButton, loadButtonText, loadMoreText])
63
+
50
64
  const reactId = useId()
51
65
 
52
66
  const selectId = useMemo(() => reactId.replace(/:/g, ''), [reactId])
@@ -95,16 +109,23 @@ const Select = forwardRef(({
95
109
  })
96
110
  }, [alwaysOpen, ownBehavior])
97
111
 
98
- const {normalizedOptions, selected, selectOption, clear, hasOptions, active, selectedValue, disabled, loading, error, placeholder, invalidOption, options, value, defaultValue, isControlled, emptyText, disabledText, loadingText, errorText} = useSelectLogic({...props, visibility, setVisibility, jsxOptions})
112
+ const {normalizedOptions, selected, selectOption, clear, hasOptions, active, selectedValue, disabled, loading, error, placeholder, invalidOption, emptyText, disabledText, loadingText, errorText} = useSelectLogic({...props, visibility, setVisibility, jsxOptions, hasMore,loadButton, loadingTitle, loadMore, loadMoreText, setLoadingTitle, childrenFirst})
99
113
 
100
114
  // event handler functions for interacting with the select
101
- const {handleBlur, handleFocus, handleToggle, handleKeyDown, highlightedIndex, setHighlightedIndex} = useSelect({
115
+ const {handleListScroll, handleBlur, handleFocus, handleToggle, handleKeyDown, highlightedIndex, setHighlightedIndex} = useSelect({
116
+ setLoadingTitle,
117
+ loadButton,
118
+ loadButtonText,
119
+ hasMore,
120
+ loadMore,
102
121
  disabled,
103
122
  isOpen: visibility,
104
123
  setIsOpen: setVisibility,
105
124
  options: normalizedOptions,
106
- selectOption: selectOption,
107
- selected: selected
125
+ selectOption,
126
+ selected,
127
+ loadOffset,
128
+ loadAhead
108
129
  })
109
130
 
110
131
  const [animationFinished, setAnimationFinished] = useState(false)
@@ -178,9 +199,11 @@ const Select = forwardRef(({
178
199
 
179
200
  if (index === highlightedIndex) optionClass += ' rac-highlighted'
180
201
 
181
- if (element.disabled) optionClass += ' rac-disabled-option'
202
+ if (element.disabled || element.loading) optionClass += ' rac-disabled-option'
203
+
204
+ if (element.invalid) optionClass += ' rac-invalid-option'
182
205
 
183
- if (element.isInvalid) optionClass += ' rac-invalid-option'
206
+ if (element.loadMore && loadingTitle == loadMoreText) optionClass += ' rac-loading-option'
184
207
 
185
208
  if (typeof element.raw === 'boolean') {
186
209
  optionClass += element.raw ? ' rac-true-option' : ' rac-false-option'
@@ -193,45 +216,31 @@ const Select = forwardRef(({
193
216
  return (
194
217
  <div
195
218
  className={optionClass}
196
- onClick={(e) => selectOption(element, e)}
197
- onMouseEnter={() => !element.disabled && setHighlightedIndex(index)}
219
+ onClick={(e) => {
220
+ if (element.loading) {
221
+ e.stopPropagation()
222
+ return
223
+ }
224
+ selectOption(element, e)
225
+ }}
226
+ onMouseEnter={() => (!element.disabled && !element.loading) && setHighlightedIndex(index)}
198
227
  key={element.id}
199
228
  id={optionId}
200
229
  role='option'
201
230
  aria-selected={selected?.id === element.id}
202
- aria-disabled={element.disabled}
231
+ aria-disabled={element.disabled || element.loading}
232
+ data-loading={element.loading}
203
233
  >
204
234
  {element.jsx ?? element.name}
235
+ {element.loading && (
236
+ <span className='rac-loading-dots'>
237
+ <i/><i/><i/>
238
+ </span>
239
+ )}
205
240
  </div>
206
241
  )
207
242
  }), [normalizedOptions, selectOption, selectId, selected, highlightedIndex])
208
243
 
209
- useEffect(() => {
210
- if (process.env.NODE_ENV !== 'production') {
211
-
212
- const receivedType = typeof options
213
- if (options && typeof options !== 'object') {
214
- console.error(
215
- `%c[Select Library]:%c Invalid prop %coptions%c.\n` +
216
- `Expected %cArray%c or %cObject%c, but received %c${receivedType}%c.\n`,
217
- 'color: #ff4d4f; font-weight: bold;', 'color: default;',
218
- 'color: #1890ff; font-weight: bold;', 'color: default;',
219
- 'color: #52c41a; font-weight: bold;', 'color: default;',
220
- 'color: #52c41a; font-weight: bold;', 'color: default;',
221
- 'color: #ff4d4f; font-weight: bold;', 'color: default;'
222
- )
223
- }
224
-
225
- if (isControlled && defaultValue !== undefined) {
226
- console.warn(
227
- `%c[Select Library]:%c .\n` +
228
- ``,
229
- 'color: #faad14; font-weight: bold;', 'color: default;'
230
- )
231
- }
232
- }
233
- }, [options, value, defaultValue, isControlled])
234
-
235
244
  return(
236
245
  <SelectContext.Provider
237
246
  value={{registerOption, unregisterOption}}
@@ -294,8 +303,12 @@ const Select = forwardRef(({
294
303
  style={{display: 'grid'}}
295
304
  >
296
305
  {renderIcon(ClearIcon, {
297
- className: 'rac-select-cancel',
298
- onClick: (e) => clear(e)
306
+ className: 'rac-select-cancel',
307
+ onMouseDown: (e) => {
308
+ e.preventDefault()
309
+ e.stopPropagation()
310
+ },
311
+ onClick: clear
299
312
  })}
300
313
  </SlideLeft>
301
314
  <SlideLeft
@@ -323,11 +336,29 @@ const Select = forwardRef(({
323
336
  animateOpacity={animateOpacity}
324
337
  >
325
338
  <div
339
+ onScroll={handleListScroll}
340
+ tabIndex='-1'
326
341
  className='rac-select-list'
327
342
  role='listbox'
328
343
  aria-label='Options'
329
344
  >
330
345
  {renderOptions}
346
+ {!loadButton && hasMore ?
347
+ <div
348
+ onClick={e => e.stopPropagation()}
349
+ onMouseEnter={e => e.preventDefault()}
350
+ className='rac-select-option rac-disabled-option rac-loading-option'
351
+ >
352
+ <span
353
+ className='rac-loading-option-title'
354
+ >
355
+ Loading
356
+ </span>
357
+ <span className='rac-loading-dots'>
358
+ <i/><i/><i/>
359
+ </span>
360
+ </div>
361
+ : null}
331
362
  </div>
332
363
  </Options>
333
364
  </div>
package/src/useSelect.jsx CHANGED
@@ -6,12 +6,61 @@ function useSelect({
6
6
  setIsOpen,
7
7
  options,
8
8
  selectOption,
9
- selected
9
+ selected,
10
+ hasMore,
11
+ loadMore,
12
+ loadButton,
13
+ loadButtonText,
14
+ setLoadingTitle,
15
+ loadOffset,
16
+ loadAhead
10
17
  }) {
11
18
  const justFocused = useRef(false)
12
19
  const lastWindowFocusTime = useRef(0)
13
20
  const [highlightedIndex, setHighlightedIndex] = useState(-1)
14
21
 
22
+ const loadingTriggered = useRef(false)
23
+
24
+ const prevOptionsLength = useRef(options?.length || 0)
25
+
26
+ useEffect(() => {
27
+ if (options && options.length !== prevOptionsLength.current) {
28
+ loadingTriggered.current = false
29
+ prevOptionsLength.current = options.length
30
+ loadButton && setLoadingTitle(loadButtonText)
31
+ }
32
+ }, [options])
33
+
34
+ const safeLoadMore = useCallback(() => {
35
+ if (!hasMore || loadingTriggered.current) return
36
+
37
+ loadingTriggered.current = true
38
+ loadMore()
39
+ }, [hasMore, loadMore])
40
+
41
+ const handleListScroll = useCallback((e) => {
42
+ if (loadButton || !hasMore || loadingTriggered.current) return
43
+
44
+ const {scrollTop, scrollHeight, clientHeight} = e.currentTarget
45
+
46
+ const threshold = loadOffset
47
+
48
+ if (scrollHeight - scrollTop <= clientHeight + threshold) {
49
+ safeLoadMore()
50
+ }
51
+ }, [loadButton, hasMore, safeLoadMore])
52
+
53
+ useEffect(() => {
54
+ if (loadButton) return
55
+
56
+ if (isOpen && hasMore && highlightedIndex >= 0) {
57
+ const threshold = loadAhead
58
+ if (highlightedIndex >= options.length - threshold) {
59
+ safeLoadMore()
60
+ }
61
+ }
62
+ }, [loadButton, highlightedIndex, isOpen, hasMore, options.length, safeLoadMore])
63
+
15
64
  useEffect(() => {
16
65
  const handleWindowFocus = () => {
17
66
  lastWindowFocusTime.current = Date.now()
@@ -22,6 +71,10 @@ function useSelect({
22
71
 
23
72
  useEffect(() => {
24
73
  if (isOpen) {
74
+ if (highlightedIndex >= 0 && highlightedIndex < options.length) {
75
+ return
76
+ }
77
+
25
78
  let index = -1
26
79
  if (selected) {
27
80
  const selectedIndex = options.findIndex(o => o.id === selected.id && !o.disabled)
@@ -34,7 +87,7 @@ function useSelect({
34
87
  } else {
35
88
  setHighlightedIndex(-1)
36
89
  }
37
- }, [isOpen, selected, options])
90
+ }, [isOpen, options])
38
91
 
39
92
  const handleBlur = useCallback((e) => {
40
93
  if (e.currentTarget.contains(e.relatedTarget)) return
@@ -60,25 +113,40 @@ function useSelect({
60
113
 
61
114
  const handleToggle = useCallback((e) => {
62
115
  if (disabled) return
63
- if (e.target.closest('.rac-select-cancel')) return
116
+ if (e.target.closest && e.target.closest('.rac-select-cancel')) return
64
117
  if (justFocused.current) return
65
118
 
66
119
  setIsOpen(!isOpen)
67
120
  }, [disabled, isOpen, setIsOpen])
68
121
 
69
122
  const getNextIndex = (current, direction) => {
70
- if (options.every(o => o.disabled)) return -1
123
+ const isNavigable = (opt) => opt && !opt.disabled && !opt.loading
124
+
125
+ if (!options.some(isNavigable)) return -1
126
+
71
127
  let next = current
72
128
  let loops = 0
129
+
73
130
  do {
74
131
  next += direction
75
- if (next < 0) next = options.length - 1
76
- if (next >= options.length) next = 0
132
+
133
+ if (next >= options.length) {
134
+ if (hasMore && !loadButton) return current
135
+ next = 0
136
+ }
137
+
138
+ if (next < 0) {
139
+ if (hasMore && !loadButton) return current
140
+ next = options.length - 1
141
+ }
142
+
77
143
  loops++
78
- } while (options[next]?.disabled && loops <= options.length)
144
+ } while (!isNavigable(options[next]) && loops <= options.length)
145
+
79
146
  return next
80
147
  }
81
148
 
149
+
82
150
  const handleKeyDown = useCallback((e) => {
83
151
  if (disabled) return
84
152
 
@@ -128,7 +196,8 @@ function useSelect({
128
196
  handleToggle,
129
197
  handleKeyDown,
130
198
  highlightedIndex,
131
- setHighlightedIndex
199
+ setHighlightedIndex,
200
+ handleListScroll
132
201
  }), [handleBlur, handleFocus, handleToggle, handleKeyDown, highlightedIndex, setHighlightedIndex])
133
202
  }
134
203
 
@@ -1,6 +1,6 @@
1
1
  import {useState, useMemo, useCallback, useId, useEffect} from 'react'
2
2
 
3
- export function useSelectLogic({
3
+ function useSelectLogic({
4
4
  options = [],
5
5
  jsxOptions = [],
6
6
  value,
@@ -17,7 +17,14 @@ export function useSelectLogic({
17
17
  disabledOption = 'Disabled option',
18
18
  emptyOption = 'Empty option',
19
19
  invalidOption = 'Invalid option',
20
- setVisibility
20
+ setVisibility,
21
+ hasMore,
22
+ loadButton,
23
+ setLoadingTitle,
24
+ loadingTitle,
25
+ loadMoreText,
26
+ loadMore,
27
+ childrenFirst
21
28
  }) {
22
29
  const stableId = useId()
23
30
  const isControlled = value !== undefined
@@ -40,7 +47,7 @@ export function useSelectLogic({
40
47
  value: val,
41
48
  userId: null,
42
49
  disabled: true,
43
- isInvalid: true,
50
+ invalid: true,
44
51
  label: invalidOption,
45
52
  original: originalItem
46
53
  })
@@ -48,25 +55,25 @@ export function useSelectLogic({
48
55
  }
49
56
 
50
57
  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
58
+ flat.push({
59
+ key: `empty-str-${flat.length}`,
60
+ value: '',
61
+ userId: null,
62
+ disabled: true,
63
+ label: emptyOption,
64
+ original: originalItem
58
65
  })
59
66
  return
60
67
  }
61
68
 
62
69
  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
70
+ flat.push({
71
+ key: `empty-${flat.length}`,
72
+ value: null,
73
+ userId: null,
74
+ disabled: true,
75
+ label: emptyOption,
76
+ original: originalItem
70
77
  })
71
78
  return
72
79
  }
@@ -80,17 +87,6 @@ export function useSelectLogic({
80
87
  original: originalItem
81
88
  })
82
89
  return
83
- }
84
-
85
- if (val && typeof val === 'object' && !Array.isArray(val)) {
86
- flat.push({
87
- key: val.id ?? val.value ?? val.name ?? key ?? `obj-${flat.length}`,
88
- value: val,
89
- userId: computedUserId,
90
- disabled: !!val.disabled,
91
- label: val.name ?? val.label ?? String(key),
92
- original: originalItem
93
- })
94
90
  } else {
95
91
  flat.push({
96
92
  key: key ?? `opt-${flat.length}`,
@@ -105,15 +101,30 @@ export function useSelectLogic({
105
101
  if (Array.isArray(options)) {
106
102
  options.forEach((item, index) => {
107
103
  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 })
104
+ flat.push({key: `dis-${index}`, value: null, userId: null, disabled: true, label: disabledOption, original: item})
109
105
  } else if (isOptionObject(item)) {
110
- const stableUserId = item.id ?? (typeof item.value !== 'object' ? item.value : (item.label ?? item.name ?? item.value));
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
+
111
122
  flat.push({
112
123
  key: item.id ?? item.value ?? item.name ?? `opt-${index}`,
113
124
  value: item.value !== undefined ? item.value : (item.id !== undefined ? item.id : item),
114
125
  userId: stableUserId,
115
- disabled: !!item.disabled,
116
- label: item.name ?? item.label ?? String(item.id ?? item.value),
126
+ disabled: hasNoContent || !!item.disabled,
127
+ label: finalLabel,
117
128
  original: item
118
129
  })
119
130
  } else if (item && typeof item === 'object' && !Array.isArray(item)) {
@@ -133,22 +144,44 @@ export function useSelectLogic({
133
144
  raw: item.value,
134
145
  original: item.original,
135
146
  disabled: item.disabled,
136
- isInvalid: item.isInvalid,
147
+ invalid: item.invalid,
137
148
  type: typeof item.value === 'boolean' ? 'boolean' : 'normal'
138
149
  }))
139
150
 
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
- }))
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
+
157
+ return {
158
+ ...opt,
159
+ id: `jsx-${stableId.replace(/:/g, '')}-${opt.id}-${index}`,
160
+ userId: opt.id,
161
+ raw: opt.value,
162
+ original: opt.value,
163
+ name: isActuallyEmpty ? emptyOption : opt.label,
164
+ disabled: opt.disabled || isActuallyEmpty,
165
+ type: typeof opt.value === 'boolean' ? 'boolean' : 'normal'
166
+ }
167
+ })
168
+
169
+ const combined = childrenFirst ? [...jsxOpts, ...propOpts] : [...propOpts, ...jsxOpts]
149
170
 
150
- return [...propOpts, ...jsxOpts]
151
- }, [options, jsxOptions, stableId, emptyOption, disabledOption])
171
+ if (hasMore && loadButton) {
172
+ const isLoading = loadingTitle === loadMoreText
173
+
174
+ combined.push({
175
+ id: 'special-load-more-id',
176
+ name: loadingTitle,
177
+ loadMore: true,
178
+ loading: isLoading,
179
+ type: 'special'
180
+ })
181
+ }
182
+
183
+ return combined
184
+ }, [options, jsxOptions, stableId, emptyOption, disabledOption, hasMore, loadButton, loadingTitle, loadMoreText])
152
185
 
153
186
  const findIdByValue = useCallback((val) => {
154
187
  if (val === undefined || val === null) return null
@@ -189,9 +222,15 @@ export function useSelectLogic({
189
222
  }, [selectedId, normalizedOptions])
190
223
 
191
224
  const selectOption = useCallback((option, e) => {
192
- if (option.disabled) {
225
+ if (option.disabled || option.loadMore) {
193
226
  e?.stopPropagation()
194
227
  e?.preventDefault()
228
+
229
+ if (loadingTitle !== loadMoreText) {
230
+ setLoadingTitle(loadMoreText)
231
+ loadMore()
232
+ }
233
+
195
234
  return
196
235
  }
197
236
 
@@ -200,9 +239,7 @@ export function useSelectLogic({
200
239
  setVisibility(false)
201
240
  }, [onChange, setVisibility])
202
241
 
203
- const clear = useCallback((e) => {
204
- e.preventDefault()
205
- e.stopPropagation()
242
+ const clear = useCallback(() => {
206
243
  setSelectedId(null)
207
244
  onChange?.(null, null)
208
245
  }, [onChange])
@@ -215,4 +252,6 @@ export function useSelectLogic({
215
252
  placeholder, emptyText, disabledText, loadingText, errorText,
216
253
  disabledOption, emptyOption, invalidOption, disabled, loading, error
217
254
  }
218
- }
255
+ }
256
+
257
+ export default useSelectLogic