@stack-spot/citric-react 0.42.0-beta.0 → 0.43.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.
- package/CHANGELOG.md +13 -13
- package/dist/citric.css +2926 -2920
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +1 -1
- package/dist/components/Alert.d.ts +1 -1
- package/dist/components/Alert.js +1 -1
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/Autocomplete.d.ts +370 -0
- package/dist/components/Autocomplete.d.ts.map +1 -0
- package/dist/components/{Autocomplete/Autocomplete.js → Autocomplete.js} +163 -98
- package/dist/components/Autocomplete.js.map +1 -0
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +1 -1
- package/dist/components/AvatarGroup.d.ts +1 -1
- package/dist/components/AvatarGroup.js +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.js +1 -1
- package/dist/components/Blockquote.d.ts +1 -1
- package/dist/components/Blockquote.js +1 -1
- package/dist/components/Breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb.js +1 -1
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Button.js +1 -1
- package/dist/components/ButtonLink.d.ts +1 -1
- package/dist/components/ButtonLink.js +1 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +1 -1
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox.js +2 -2
- package/dist/components/Checkbox.js.map +1 -1
- package/dist/components/CheckboxGroup.d.ts +1 -1
- package/dist/components/CheckboxGroup.js +1 -1
- package/dist/components/Circle.d.ts +1 -1
- package/dist/components/Circle.js +1 -1
- package/dist/components/Divider.d.ts +1 -1
- package/dist/components/Divider.js +1 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorMessage.d.ts +1 -1
- package/dist/components/ErrorMessage.js +1 -1
- package/dist/components/FallbackBoundary.d.ts +1 -1
- package/dist/components/FallbackBoundary.js +1 -1
- package/dist/components/Favorite.d.ts +1 -1
- package/dist/components/Favorite.js +1 -1
- package/dist/components/FieldGroup.d.ts +1 -1
- package/dist/components/FieldGroup.js +1 -1
- package/dist/components/Form.d.ts +2 -2
- package/dist/components/Form.js +1 -1
- package/dist/components/FormGroup.d.ts +1 -1
- package/dist/components/FormGroup.js +1 -1
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/Icon.js +1 -1
- package/dist/components/IconBox.d.ts +3 -3
- package/dist/components/IconBox.js +1 -1
- package/dist/components/ImageBox.d.ts +3 -3
- package/dist/components/ImageBox.js +1 -1
- package/dist/components/ImageWithFallback.d.ts +1 -1
- package/dist/components/ImageWithFallback.js +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +1 -1
- package/dist/components/Link.d.ts +1 -1
- package/dist/components/Link.js +1 -1
- package/dist/components/LoadingPanel.d.ts +1 -1
- package/dist/components/LoadingPanel.js +1 -1
- package/dist/components/MenuOverlay/Menu.d.ts +1 -1
- package/dist/components/MenuOverlay/Menu.js +1 -1
- package/dist/components/MenuOverlay/index.d.ts +1 -1
- package/dist/components/MenuOverlay/index.js +1 -1
- package/dist/components/Overlay/index.d.ts +1 -1
- package/dist/components/Overlay/index.js +1 -1
- package/dist/components/Pagination.d.ts +1 -1
- package/dist/components/Pagination.js +1 -1
- package/dist/components/ProgressBar.d.ts +1 -1
- package/dist/components/ProgressBar.js +1 -1
- package/dist/components/ProgressCircular.d.ts +1 -1
- package/dist/components/ProgressCircular.js +1 -1
- package/dist/components/RadioGroup.d.ts +1 -1
- package/dist/components/RadioGroup.js +1 -1
- package/dist/components/Rating.d.ts +1 -1
- package/dist/components/Rating.js +1 -1
- package/dist/components/Select/MultiSelect.d.ts +1 -1
- package/dist/components/Select/MultiSelect.js +1 -1
- package/dist/components/Select/RichSelect.d.ts +1 -1
- package/dist/components/Select/RichSelect.js +1 -1
- package/dist/components/Select/SimpleSelect.d.ts +1 -1
- package/dist/components/Select/SimpleSelect.js +1 -1
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectBox.d.ts +9 -1
- package/dist/components/SelectBox.d.ts.map +1 -1
- package/dist/components/SelectBox.js +6 -5
- package/dist/components/SelectBox.js.map +1 -1
- package/dist/components/Skeleton.d.ts +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/Slider.d.ts +1 -1
- package/dist/components/Slider.js +1 -1
- package/dist/components/SmartTable.d.ts +1 -1
- package/dist/components/SmartTable.js +1 -1
- package/dist/components/Stepper.d.ts +1 -1
- package/dist/components/Stepper.js +1 -1
- package/dist/components/Table.d.ts +3 -3
- package/dist/components/Table.js +1 -1
- package/dist/components/Tabs/index.d.ts +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Textarea.d.ts +1 -1
- package/dist/components/Textarea.js +1 -1
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +1 -1
- package/dist/context/CitricProvider.d.ts +1 -1
- package/dist/context/CitricProvider.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay.js +1 -1
- package/dist/theme.css +415 -415
- package/dist/utils/css.js +1 -1
- package/dist/utils/css.js.map +1 -1
- package/package.json +1 -1
- package/scripts/build-css.ts +49 -49
- package/src/components/Accordion.tsx +130 -130
- package/src/components/Alert.tsx +24 -24
- package/src/components/AsyncContent.tsx +75 -75
- package/src/components/{Autocomplete/Autocomplete.tsx → Autocomplete.tsx} +403 -159
- package/src/components/Avatar.tsx +45 -45
- package/src/components/AvatarGroup.tsx +49 -49
- package/src/components/Badge.tsx +47 -47
- package/src/components/Blockquote.tsx +18 -18
- package/src/components/Breadcrumb.tsx +33 -33
- package/src/components/Button.tsx +105 -105
- package/src/components/ButtonLink.tsx +45 -45
- package/src/components/Card.tsx +68 -68
- package/src/components/Checkbox.tsx +51 -52
- package/src/components/CheckboxGroup.tsx +153 -153
- package/src/components/Circle.tsx +43 -43
- package/src/components/CitricComponent.ts +47 -47
- package/src/components/Divider.tsx +24 -24
- package/src/components/ErrorBoundary.tsx +75 -75
- package/src/components/ErrorMessage.tsx +11 -11
- package/src/components/FallbackBoundary.tsx +40 -40
- package/src/components/Favorite.tsx +57 -57
- package/src/components/FieldGroup.tsx +46 -46
- package/src/components/Form.tsx +36 -36
- package/src/components/FormGroup.tsx +57 -57
- package/src/components/Icon.tsx +35 -35
- package/src/components/IconBox.tsx +134 -134
- package/src/components/ImageBox.tsx +125 -125
- package/src/components/ImageWithFallback.tsx +65 -65
- package/src/components/Input.tsx +49 -49
- package/src/components/Link.tsx +55 -55
- package/src/components/LoadingPanel.tsx +12 -12
- package/src/components/MenuOverlay/Menu.tsx +158 -158
- package/src/components/MenuOverlay/context.ts +20 -20
- package/src/components/MenuOverlay/index.tsx +55 -55
- package/src/components/MenuOverlay/keyboard.ts +60 -60
- package/src/components/MenuOverlay/types.ts +171 -171
- package/src/components/Overlay/context.ts +10 -10
- package/src/components/Overlay/index.tsx +182 -182
- package/src/components/Overlay/types.ts +75 -75
- package/src/components/Pagination.tsx +133 -133
- package/src/components/ProgressBar.tsx +45 -45
- package/src/components/ProgressCircular.tsx +45 -45
- package/src/components/RadioGroup.tsx +147 -147
- package/src/components/Rating.tsx +98 -98
- package/src/components/Select/MultiSelect.tsx +217 -217
- package/src/components/Select/RichSelect.tsx +128 -128
- package/src/components/Select/SimpleSelect.tsx +73 -73
- package/src/components/Select/hooks.ts +133 -133
- package/src/components/Select/index.tsx +35 -35
- package/src/components/Select/types.ts +134 -134
- package/src/components/SelectBox.tsx +181 -167
- package/src/components/Skeleton.tsx +53 -53
- package/src/components/Slider.tsx +89 -89
- package/src/components/SmartTable.tsx +227 -227
- package/src/components/Stepper.tsx +163 -163
- package/src/components/Table.tsx +234 -234
- package/src/components/Tabs/TabController.ts +54 -54
- package/src/components/Tabs/index.tsx +106 -106
- package/src/components/Tabs/types.ts +67 -67
- package/src/components/Tabs/utils.ts +6 -6
- package/src/components/Text.ts +111 -111
- package/src/components/Textarea.tsx +27 -27
- package/src/components/Tooltip.tsx +83 -83
- package/src/components/layout.tsx +101 -101
- package/src/context/CitricContext.tsx +4 -4
- package/src/context/CitricProvider.tsx +14 -14
- package/src/context/hooks.ts +6 -6
- package/src/index.ts +59 -59
- package/src/overlay.ts +348 -348
- package/src/types.ts +235 -235
- package/src/utils/ValueController.ts +28 -28
- package/src/utils/acessibility.ts +92 -92
- package/src/utils/checkbox.ts +121 -121
- package/src/utils/css.ts +119 -119
- package/src/utils/options.ts +9 -9
- package/src/utils/radio.ts +93 -93
- package/src/utils/react.ts +6 -6
- package/src/utils/time.ts +5 -5
- package/tsconfig.json +10 -10
- package/dist/components/Autocomplete/Autocomplete.d.ts +0 -211
- package/dist/components/Autocomplete/Autocomplete.d.ts.map +0 -1
- package/dist/components/Autocomplete/Autocomplete.js.map +0 -1
- package/dist/components/Autocomplete/index.d.ts +0 -3
- package/dist/components/Autocomplete/index.d.ts.map +0 -1
- package/dist/components/Autocomplete/index.js +0 -2
- package/dist/components/Autocomplete/index.js.map +0 -1
- package/src/components/Autocomplete/index.ts +0 -3
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
import { ColorPaletteName, ColorSchemeName, listToClass } from '@stack-spot/portal-theme'
|
|
2
2
|
import { useTranslate } from '@stack-spot/portal-translate'
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
-
import { applyCSSVariable } from '
|
|
5
|
-
import { defaultRenderKey, defaultRenderLabel } from '
|
|
6
|
-
import { withRef } from '
|
|
7
|
-
import { Badge } from '
|
|
8
|
-
import { Checkbox } from '
|
|
9
|
-
import { CitricComponent } from '
|
|
10
|
-
import { IconButton } from '
|
|
11
|
-
import { ProgressCircular } from '
|
|
12
|
-
import { useDisabledEffect, useFocusEffect } from '
|
|
13
|
-
import { Row } from '
|
|
4
|
+
import { applyCSSVariable } from '../utils/css'
|
|
5
|
+
import { defaultRenderKey, defaultRenderLabel } from '../utils/options'
|
|
6
|
+
import { withRef } from '../utils/react'
|
|
7
|
+
import { Badge } from './Badge'
|
|
8
|
+
import { Checkbox } from './Checkbox'
|
|
9
|
+
import { CitricComponent } from './CitricComponent'
|
|
10
|
+
import { IconButton } from './IconBox'
|
|
11
|
+
import { ProgressCircular } from './ProgressCircular'
|
|
12
|
+
import { useDisabledEffect, useFocusEffect } from './Select/hooks'
|
|
13
|
+
import { Row } from './layout'
|
|
14
|
+
|
|
15
|
+
export interface CustomSelectedTagsConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Color scheme for the tags (badges).
|
|
18
|
+
*/
|
|
19
|
+
colorScheme?: ColorSchemeName,
|
|
20
|
+
/**
|
|
21
|
+
* Color palette for the tags (badges).
|
|
22
|
+
*/
|
|
23
|
+
colorPalette?: ColorPaletteName,
|
|
24
|
+
/**
|
|
25
|
+
* Appearance of the tags (badges).
|
|
26
|
+
* @default 'circle'
|
|
27
|
+
*/
|
|
28
|
+
appearance?: 'square' | 'circle',
|
|
29
|
+
/**
|
|
30
|
+
* Maximum number of tags to show before displaying "+N more".
|
|
31
|
+
*/
|
|
32
|
+
maxItems?: number,
|
|
33
|
+
}
|
|
14
34
|
|
|
15
35
|
export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
16
36
|
/**
|
|
@@ -19,14 +39,37 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
19
39
|
options: T[],
|
|
20
40
|
/**
|
|
21
41
|
* The current value(s) selected.
|
|
22
|
-
* - Single selection: T |
|
|
42
|
+
* - Single selection: T | undefined
|
|
23
43
|
* - Multiple selection: T[]
|
|
24
44
|
*/
|
|
25
|
-
value: Multiple extends true ? T[] : (T |
|
|
45
|
+
value: Multiple extends true ? T[] : (T | undefined),
|
|
26
46
|
/**
|
|
27
|
-
* Callback fired when the value changes.
|
|
47
|
+
* Callback fired when the value changes (user selects/removes an option).
|
|
48
|
+
* This is the main callback for getting the final selected value(s).
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* // Single selection
|
|
53
|
+
* <Autocomplete
|
|
54
|
+
* value={user}
|
|
55
|
+
* onChange={(selectedUser) => {
|
|
56
|
+
* console.log('Selected:', selectedUser)
|
|
57
|
+
* setUser(selectedUser)
|
|
58
|
+
* }}
|
|
59
|
+
* />
|
|
60
|
+
*
|
|
61
|
+
* // Multiple selection
|
|
62
|
+
* <Autocomplete
|
|
63
|
+
* multiple
|
|
64
|
+
* value={selectedUsers}
|
|
65
|
+
* onChange={(users) => {
|
|
66
|
+
* console.log('Selected users:', users)
|
|
67
|
+
* setSelectedUsers(users)
|
|
68
|
+
* }}
|
|
69
|
+
* />
|
|
70
|
+
* ```
|
|
28
71
|
*/
|
|
29
|
-
onChange: Multiple extends true ? (value: T[]) => void : (value: T |
|
|
72
|
+
onChange: Multiple extends true ? (value: T[]) => void : (value: T | undefined) => void,
|
|
30
73
|
/**
|
|
31
74
|
* If true, enables multiple selection mode.
|
|
32
75
|
* @default false
|
|
@@ -50,20 +93,70 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
50
93
|
onCreate?: (inputValue: string) => void,
|
|
51
94
|
/**
|
|
52
95
|
* Function to create a new option object from the input value.
|
|
53
|
-
*
|
|
96
|
+
* Used when creatable is true and onCreate is NOT defined.
|
|
97
|
+
* Allows automatic option creation on Enter key.
|
|
98
|
+
*
|
|
99
|
+
* Note: This prop has no effect when onCreate is defined, as onCreate takes precedence.
|
|
100
|
+
*
|
|
101
|
+
* @param inputValue - The text typed by the user
|
|
102
|
+
* @returns A new option object
|
|
54
103
|
*
|
|
55
104
|
* @example
|
|
56
105
|
* ```tsx
|
|
57
|
-
*
|
|
106
|
+
* // Auto-create tags on Enter
|
|
107
|
+
* <Autocomplete
|
|
108
|
+
* multiple
|
|
109
|
+
* creatable
|
|
110
|
+
* freeSolo
|
|
111
|
+
* getOptionFromInput={(text) => ({
|
|
112
|
+
* id: Date.now(),
|
|
113
|
+
* name: text,
|
|
114
|
+
* isCustom: true
|
|
115
|
+
* })}
|
|
116
|
+
* />
|
|
58
117
|
* ```
|
|
59
118
|
*/
|
|
60
119
|
getOptionFromInput?: (inputValue: string) => T,
|
|
61
120
|
/**
|
|
62
|
-
* The input value (controlled).
|
|
121
|
+
* The input value (controlled mode).
|
|
122
|
+
* Use this when you need full control over the input text.
|
|
123
|
+
* Usually used with onInputChange for controlled components.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```tsx
|
|
127
|
+
* const [inputValue, setInputValue] = useState('')
|
|
128
|
+
*
|
|
129
|
+
* <Autocomplete
|
|
130
|
+
* inputValue={inputValue}
|
|
131
|
+
* onInputChange={setInputValue}
|
|
132
|
+
* options={options}
|
|
133
|
+
* />
|
|
134
|
+
* ```
|
|
63
135
|
*/
|
|
64
136
|
inputValue?: string,
|
|
65
137
|
/**
|
|
66
|
-
* Callback fired when the input
|
|
138
|
+
* Callback fired when the input text changes (user types).
|
|
139
|
+
* Use this to control the input value or perform side effects like API calls.
|
|
140
|
+
* Different from onChange which fires when an option is selected.
|
|
141
|
+
*
|
|
142
|
+
* @param value - The current text in the input field
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```tsx
|
|
146
|
+
* // Debounced API search
|
|
147
|
+
* <Autocomplete
|
|
148
|
+
* onInputChange={(text) => {
|
|
149
|
+
* console.log('User typed:', text)
|
|
150
|
+
* debouncedSearch(text)
|
|
151
|
+
* }}
|
|
152
|
+
* />
|
|
153
|
+
*
|
|
154
|
+
* // Controlled input
|
|
155
|
+
* <Autocomplete
|
|
156
|
+
* inputValue={inputValue}
|
|
157
|
+
* onInputChange={(text) => setInputValue(text.toUpperCase())}
|
|
158
|
+
* />
|
|
159
|
+
* ```
|
|
67
160
|
*/
|
|
68
161
|
onInputChange?: (value: string) => void,
|
|
69
162
|
/**
|
|
@@ -74,18 +167,76 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
74
167
|
/**
|
|
75
168
|
* A function to generate a unique key for each option.
|
|
76
169
|
*/
|
|
77
|
-
renderKey?: (option: T) => string | number,
|
|
170
|
+
renderKey?: (option: T) => string | number | undefined,
|
|
78
171
|
/**
|
|
79
172
|
* A function to render an option in the dropdown.
|
|
80
173
|
*/
|
|
81
174
|
renderOption?: (option: T) => React.ReactNode,
|
|
82
175
|
/**
|
|
83
|
-
*
|
|
176
|
+
* Custom function to render the selected values display area in multiple mode.
|
|
177
|
+
* When defined, gives you full control over how selected values are displayed.
|
|
178
|
+
* The customSelectedTags prop has no effect when this is defined.
|
|
179
|
+
*
|
|
180
|
+
* @param values - Array of selected options
|
|
181
|
+
* @param onRemove - Function to call when user wants to remove an option
|
|
182
|
+
* @returns React element to display selected values
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```tsx
|
|
186
|
+
* <Autocomplete
|
|
187
|
+
* multiple
|
|
188
|
+
* renderSelected={(values, onRemove) => (
|
|
189
|
+
* <div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
|
190
|
+
* {values.map(user => (
|
|
191
|
+
* <div key={user.id} style={{ background: '#e3f2fd', padding: '4px 8px', borderRadius: '12px' }}>
|
|
192
|
+
* <Avatar src={user.avatar} size="xs" />
|
|
193
|
+
* {user.name}
|
|
194
|
+
* <button onClick={() => onRemove(user)}>×</button>
|
|
195
|
+
* </div>
|
|
196
|
+
* ))}
|
|
197
|
+
* </div>
|
|
198
|
+
* )}
|
|
199
|
+
* />
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
renderSelected?: (values: T[], onRemove: (option: T) => void) => React.ReactElement,
|
|
203
|
+
/**
|
|
204
|
+
* Configuration for the default selected tags appearance in multiple mode.
|
|
205
|
+
* Has no effect when renderSelected is defined.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```tsx
|
|
209
|
+
* <Autocomplete
|
|
210
|
+
* multiple
|
|
211
|
+
* customSelectedTags={{
|
|
212
|
+
* colorScheme: 'primary',
|
|
213
|
+
* appearance: 'square',
|
|
214
|
+
* maxItems: 3
|
|
215
|
+
* }}
|
|
216
|
+
* />
|
|
217
|
+
* ```
|
|
84
218
|
*/
|
|
85
|
-
|
|
219
|
+
customSelectedTags?: CustomSelectedTagsConfig,
|
|
86
220
|
/**
|
|
87
221
|
* Custom filter function for options.
|
|
88
|
-
*
|
|
222
|
+
* When not set, the filter will use the text returned by renderLabel (case-insensitive includes).
|
|
223
|
+
*
|
|
224
|
+
* @param options - The full list of options
|
|
225
|
+
* @param inputValue - The current input text
|
|
226
|
+
* @returns Filtered array of options
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```tsx
|
|
230
|
+
* // Search by name OR email
|
|
231
|
+
* <Autocomplete
|
|
232
|
+
* filterOptions={(options, input) =>
|
|
233
|
+
* options.filter(user =>
|
|
234
|
+
* user.name.toLowerCase().includes(input.toLowerCase()) ||
|
|
235
|
+
* user.email.toLowerCase().includes(input.toLowerCase())
|
|
236
|
+
* )
|
|
237
|
+
* }
|
|
238
|
+
* />
|
|
239
|
+
* ```
|
|
89
240
|
*/
|
|
90
241
|
filterOptions?: (options: T[], inputValue: string) => T[],
|
|
91
242
|
/**
|
|
@@ -106,11 +257,6 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
106
257
|
* Maximum height for the dropdown panel in pixels.
|
|
107
258
|
*/
|
|
108
259
|
maxHeight?: number,
|
|
109
|
-
/**
|
|
110
|
-
* Maximum number of tags to show before truncating.
|
|
111
|
-
* Only applies when multiple is true.
|
|
112
|
-
*/
|
|
113
|
-
maxTagsToShow?: number,
|
|
114
260
|
/**
|
|
115
261
|
* If true, automatically highlights the first option.
|
|
116
262
|
* @default false
|
|
@@ -119,7 +265,30 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
119
265
|
/**
|
|
120
266
|
* If true, clears the input value when an option is selected.
|
|
121
267
|
* Only applies when multiple is true.
|
|
122
|
-
*
|
|
268
|
+
* When false, the input keeps the text after selection, useful for adding multiple similar items quickly.
|
|
269
|
+
*
|
|
270
|
+
* Note: Adding multiple tags with similar prefixes when clearOnSelect={false}, you can select "React",
|
|
271
|
+
* then easily select "React Native"
|
|
272
|
+
* without retyping "React" from scratch
|
|
273
|
+
* @default true (for multiple mode)
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```tsx
|
|
277
|
+
* // Clear input after each selection (default)
|
|
278
|
+
* <Autocomplete multiple clearOnSelect />
|
|
279
|
+
*
|
|
280
|
+
* // Keep input text after selection
|
|
281
|
+
* const [tags, setTags] = useState<Tag[]>([])
|
|
282
|
+
*
|
|
283
|
+
* <Autocomplete
|
|
284
|
+
* multiple
|
|
285
|
+
* clearOnSelect={false}
|
|
286
|
+
* value={tags}
|
|
287
|
+
* onChange={setTags}
|
|
288
|
+
* options={availableTags}
|
|
289
|
+
* renderLabel={tag => tag.name}
|
|
290
|
+
* />
|
|
291
|
+
* ```
|
|
123
292
|
*/
|
|
124
293
|
clearOnSelect?: boolean,
|
|
125
294
|
/**
|
|
@@ -127,10 +296,6 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
127
296
|
* @default true
|
|
128
297
|
*/
|
|
129
298
|
openOnFocus?: boolean,
|
|
130
|
-
/**
|
|
131
|
-
* Callback fired when an option is selected (before onChange).
|
|
132
|
-
*/
|
|
133
|
-
onSelect?: (option: T | null) => void,
|
|
134
299
|
/**
|
|
135
300
|
* Text to display when no options are available.
|
|
136
301
|
*/
|
|
@@ -159,24 +324,19 @@ export interface BaseAutocompleteProps<T, Multiple extends boolean = false> {
|
|
|
159
324
|
*/
|
|
160
325
|
scrollEndMargin?: number,
|
|
161
326
|
/**
|
|
162
|
-
* Color scheme for the
|
|
163
|
-
*
|
|
327
|
+
* Color scheme for the autocomplete component.
|
|
328
|
+
* Applies the theme's color scheme to the component root.
|
|
164
329
|
*/
|
|
165
|
-
|
|
330
|
+
colorScheme?: ColorSchemeName,
|
|
166
331
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*/
|
|
170
|
-
tagColorPalette?: ColorPaletteName,
|
|
171
|
-
/**
|
|
172
|
-
* Appearance of the tags (badges) in multiple mode.
|
|
173
|
-
* @default 'circle'
|
|
332
|
+
* The id attribute for the input element.
|
|
333
|
+
* Useful for associating with a label element.
|
|
174
334
|
*/
|
|
175
|
-
|
|
335
|
+
id?: string,
|
|
176
336
|
}
|
|
177
337
|
|
|
178
338
|
export type AutocompleteProps<T, Multiple extends boolean = false> =
|
|
179
|
-
Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange'
|
|
339
|
+
Omit<React.JSX.IntrinsicElements['div'], 'ref' | 'onChange'> &
|
|
180
340
|
BaseAutocompleteProps<T, Multiple>
|
|
181
341
|
|
|
182
342
|
/**
|
|
@@ -239,24 +399,22 @@ export const Autocomplete = withRef(
|
|
|
239
399
|
renderLabel = defaultRenderLabel,
|
|
240
400
|
renderKey = defaultRenderKey as (option: T) => string | number,
|
|
241
401
|
renderOption,
|
|
242
|
-
|
|
402
|
+
renderSelected,
|
|
403
|
+
customSelectedTags,
|
|
243
404
|
filterOptions,
|
|
244
405
|
loading = false,
|
|
245
406
|
disabled = false,
|
|
246
407
|
placeholder,
|
|
247
408
|
maxHeight,
|
|
248
|
-
maxTagsToShow,
|
|
249
409
|
autoHighlight = false,
|
|
250
410
|
clearOnSelect = multiple,
|
|
251
411
|
openOnFocus = true,
|
|
252
|
-
onSelect,
|
|
253
412
|
noOptionsText,
|
|
254
413
|
loadingText,
|
|
255
414
|
onScrollEnd,
|
|
256
415
|
scrollEndMargin = 200,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
tagAppearance = 'circle',
|
|
416
|
+
colorScheme,
|
|
417
|
+
id,
|
|
260
418
|
style,
|
|
261
419
|
className,
|
|
262
420
|
...props
|
|
@@ -265,6 +423,7 @@ export const Autocomplete = withRef(
|
|
|
265
423
|
const _element = useRef<HTMLDivElement | null>(null)
|
|
266
424
|
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
267
425
|
const dropdownRef = useRef<HTMLDivElement | null>(null)
|
|
426
|
+
const isNavigatingWithKeyboard = useRef(false)
|
|
268
427
|
const element = (ref as React.RefObject<HTMLDivElement>) ?? _element
|
|
269
428
|
|
|
270
429
|
const [open, setOpen] = useState(false)
|
|
@@ -306,13 +465,18 @@ export const Autocomplete = withRef(
|
|
|
306
465
|
const defaultFilter = useCallback((opts: T[], input: string) => {
|
|
307
466
|
if (!input) return opts
|
|
308
467
|
return opts.filter(option =>
|
|
309
|
-
renderLabel(option)
|
|
468
|
+
renderLabel(option)?.toLowerCase()?.includes(input?.toLowerCase()),
|
|
310
469
|
)
|
|
311
470
|
}, [renderLabel])
|
|
312
471
|
|
|
313
472
|
const filter = filterOptions ?? defaultFilter
|
|
314
473
|
|
|
315
|
-
const filteredOptions = useMemo(() =>
|
|
474
|
+
const filteredOptions = useMemo(() => {
|
|
475
|
+
if (!multiple && value && renderLabel(value as T) === inputValue) {
|
|
476
|
+
return options
|
|
477
|
+
}
|
|
478
|
+
return filter(options, inputValue)
|
|
479
|
+
}, [options, inputValue, filter, multiple, value, renderLabel])
|
|
316
480
|
|
|
317
481
|
const showCreateOption = useMemo(() => {
|
|
318
482
|
if (!creatable || !onCreate || !inputValue.trim()) return false
|
|
@@ -343,8 +507,6 @@ export const Autocomplete = withRef(
|
|
|
343
507
|
}, [value, multiple, renderKey])
|
|
344
508
|
|
|
345
509
|
const handleSelect = useCallback((option: T) => {
|
|
346
|
-
if (onSelect) onSelect(option)
|
|
347
|
-
|
|
348
510
|
if (multiple) {
|
|
349
511
|
const currentValue = value as T[]
|
|
350
512
|
const isAlreadySelected = currentValue.some(v => renderKey(v) === renderKey(option))
|
|
@@ -364,7 +526,7 @@ export const Autocomplete = withRef(
|
|
|
364
526
|
setInputValue(renderLabel(option))
|
|
365
527
|
setOpen(false)
|
|
366
528
|
}
|
|
367
|
-
}, [multiple, value, onChange, renderKey,
|
|
529
|
+
}, [multiple, value, onChange, renderKey, clearOnSelect, setInputValue, renderLabel])
|
|
368
530
|
|
|
369
531
|
const handleRemoveTag = useCallback((optionToRemove: T) => {
|
|
370
532
|
if (!multiple) return
|
|
@@ -384,6 +546,10 @@ export const Autocomplete = withRef(
|
|
|
384
546
|
setFocused(true)
|
|
385
547
|
if (openOnFocus) {
|
|
386
548
|
setOpen(true)
|
|
549
|
+
|
|
550
|
+
if (autoHighlight && filteredOptions.length > 0) {
|
|
551
|
+
setHighlightedIndex(0)
|
|
552
|
+
}
|
|
387
553
|
}
|
|
388
554
|
}
|
|
389
555
|
|
|
@@ -411,17 +577,114 @@ export const Autocomplete = withRef(
|
|
|
411
577
|
handleSelect(exactMatch)
|
|
412
578
|
}
|
|
413
579
|
}
|
|
580
|
+
} else if (!multiple && inputValue) {
|
|
581
|
+
const exactMatch = options.find(o =>
|
|
582
|
+
renderLabel(o).toLowerCase() === inputValue.toLowerCase(),
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
if (exactMatch) {
|
|
586
|
+
handleSelect(exactMatch)
|
|
587
|
+
} else {
|
|
588
|
+
if (value) {
|
|
589
|
+
setInputValue(renderLabel(value as T))
|
|
590
|
+
} else {
|
|
591
|
+
setInputValue('')
|
|
592
|
+
}
|
|
593
|
+
}
|
|
414
594
|
}
|
|
415
595
|
}
|
|
416
596
|
|
|
597
|
+
const handleCreateNewOption = useCallback(() => {
|
|
598
|
+
if (!inputValue.trim()) return false
|
|
599
|
+
|
|
600
|
+
if (onCreate) {
|
|
601
|
+
handleCreate()
|
|
602
|
+
return true
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (freeSolo && getOptionFromInput) {
|
|
606
|
+
const newOption = getOptionFromInput(inputValue.trim())
|
|
607
|
+
if (multiple) {
|
|
608
|
+
const currentValue = value as T[]
|
|
609
|
+
const isDuplicate = currentValue.some(v => renderKey(v) === renderKey(newOption))
|
|
610
|
+
if (!isDuplicate) {
|
|
611
|
+
(onChange as (value: T[]) => void)([...currentValue, newOption])
|
|
612
|
+
}
|
|
613
|
+
setInputValue('')
|
|
614
|
+
} else {
|
|
615
|
+
(onChange as (value: T | null) => void)(newOption)
|
|
616
|
+
setInputValue(renderLabel(newOption))
|
|
617
|
+
setOpen(false)
|
|
618
|
+
}
|
|
619
|
+
return true
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (freeSolo) {
|
|
623
|
+
if (multiple) {
|
|
624
|
+
const currentValue = value as T[]
|
|
625
|
+
const inputAsOption = inputValue as unknown as T
|
|
626
|
+
const isDuplicate = currentValue.some(v => renderLabel(v).toLowerCase() === inputValue.toLowerCase())
|
|
627
|
+
if (!isDuplicate) {
|
|
628
|
+
(onChange as (value: T[]) => void)([...currentValue, inputAsOption])
|
|
629
|
+
}
|
|
630
|
+
setInputValue('')
|
|
631
|
+
} else {
|
|
632
|
+
(onChange as (value: T | null) => void)(inputValue as unknown as T)
|
|
633
|
+
setOpen(false)
|
|
634
|
+
}
|
|
635
|
+
return true
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return false
|
|
639
|
+
}, [onCreate, handleCreate, freeSolo, getOptionFromInput, inputValue, multiple, value, renderKey, onChange, setInputValue, renderLabel])
|
|
640
|
+
|
|
641
|
+
const handleEnterKey = useCallback(() => {
|
|
642
|
+
if (open && highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
|
|
643
|
+
handleSelect(filteredOptions[highlightedIndex])
|
|
644
|
+
return
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (!open && filteredOptions.length === 1) {
|
|
648
|
+
handleSelect(filteredOptions[0])
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (creatable && handleCreateNewOption()) {
|
|
653
|
+
return
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (freeSolo && inputValue && !multiple) {
|
|
657
|
+
const exactMatch = options.find(o =>
|
|
658
|
+
renderLabel(o).toLowerCase() === inputValue.toLowerCase(),
|
|
659
|
+
)
|
|
660
|
+
if (exactMatch) {
|
|
661
|
+
handleSelect(exactMatch)
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}, [
|
|
665
|
+
open,
|
|
666
|
+
highlightedIndex,
|
|
667
|
+
filteredOptions,
|
|
668
|
+
handleSelect,
|
|
669
|
+
creatable,
|
|
670
|
+
handleCreateNewOption,
|
|
671
|
+
freeSolo,
|
|
672
|
+
inputValue,
|
|
673
|
+
multiple,
|
|
674
|
+
options,
|
|
675
|
+
renderLabel,
|
|
676
|
+
])
|
|
677
|
+
|
|
417
678
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
418
679
|
if (disabled) return
|
|
419
680
|
|
|
420
681
|
switch (e.key) {
|
|
421
682
|
case 'ArrowDown':
|
|
422
683
|
e.preventDefault()
|
|
684
|
+
isNavigatingWithKeyboard.current = true
|
|
423
685
|
if (!open) {
|
|
424
686
|
setOpen(true)
|
|
687
|
+
setHighlightedIndex(0)
|
|
425
688
|
} else {
|
|
426
689
|
setHighlightedIndex(prev =>
|
|
427
690
|
prev < filteredOptions.length - 1 ? prev + 1 : prev,
|
|
@@ -431,6 +694,7 @@ export const Autocomplete = withRef(
|
|
|
431
694
|
|
|
432
695
|
case 'ArrowUp':
|
|
433
696
|
e.preventDefault()
|
|
697
|
+
isNavigatingWithKeyboard.current = true
|
|
434
698
|
if (open) {
|
|
435
699
|
setHighlightedIndex(prev => prev > 0 ? prev - 1 : 0)
|
|
436
700
|
}
|
|
@@ -438,47 +702,7 @@ export const Autocomplete = withRef(
|
|
|
438
702
|
|
|
439
703
|
case 'Enter':
|
|
440
704
|
e.preventDefault()
|
|
441
|
-
|
|
442
|
-
handleSelect(filteredOptions[highlightedIndex])
|
|
443
|
-
} else if (creatable && inputValue.trim()) {
|
|
444
|
-
if (onCreate) {
|
|
445
|
-
handleCreate()
|
|
446
|
-
} else if (freeSolo && getOptionFromInput) {
|
|
447
|
-
const newOption = getOptionFromInput(inputValue.trim())
|
|
448
|
-
if (multiple) {
|
|
449
|
-
const currentValue = value as T[]
|
|
450
|
-
const isDuplicate = currentValue.some(v => renderKey(v) === renderKey(newOption))
|
|
451
|
-
if (!isDuplicate) {
|
|
452
|
-
(onChange as (value: T[]) => void)([...currentValue, newOption])
|
|
453
|
-
}
|
|
454
|
-
setInputValue('')
|
|
455
|
-
} else {
|
|
456
|
-
(onChange as (value: T | null) => void)(newOption)
|
|
457
|
-
setInputValue(renderLabel(newOption))
|
|
458
|
-
setOpen(false)
|
|
459
|
-
}
|
|
460
|
-
} else if (freeSolo) {
|
|
461
|
-
if (multiple) {
|
|
462
|
-
const currentValue = value as T[]
|
|
463
|
-
const inputAsOption = inputValue as unknown as T
|
|
464
|
-
const isDuplicate = currentValue.some(v => renderLabel(v).toLowerCase() === inputValue.toLowerCase())
|
|
465
|
-
if (!isDuplicate) {
|
|
466
|
-
(onChange as (value: T[]) => void)([...currentValue, inputAsOption])
|
|
467
|
-
}
|
|
468
|
-
setInputValue('')
|
|
469
|
-
} else {
|
|
470
|
-
(onChange as (value: T | null) => void)(inputValue as unknown as T)
|
|
471
|
-
setOpen(false)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
} else if (freeSolo && inputValue && !multiple) {
|
|
475
|
-
const exactMatch = options.find(o =>
|
|
476
|
-
renderLabel(o).toLowerCase() === inputValue.toLowerCase(),
|
|
477
|
-
)
|
|
478
|
-
if (exactMatch) {
|
|
479
|
-
handleSelect(exactMatch)
|
|
480
|
-
}
|
|
481
|
-
}
|
|
705
|
+
handleEnterKey()
|
|
482
706
|
break
|
|
483
707
|
|
|
484
708
|
case 'Escape':
|
|
@@ -493,6 +717,8 @@ export const Autocomplete = withRef(
|
|
|
493
717
|
if (multiple && !inputValue && (value as T[]).length > 0) {
|
|
494
718
|
const lastTag = (value as T[])[(value as T[]).length - 1]
|
|
495
719
|
handleRemoveTag(lastTag)
|
|
720
|
+
} else if (!multiple && !inputValue && value) {
|
|
721
|
+
(onChange as (value: T | null) => void)(null)
|
|
496
722
|
}
|
|
497
723
|
break
|
|
498
724
|
|
|
@@ -513,17 +739,14 @@ export const Autocomplete = withRef(
|
|
|
513
739
|
}
|
|
514
740
|
}
|
|
515
741
|
|
|
516
|
-
|
|
517
|
-
if (
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
if (autoHighlight && open && filteredOptions.length > 0) {
|
|
524
|
-
setHighlightedIndex(0)
|
|
742
|
+
const handleFocusAndOpen = () => {
|
|
743
|
+
if (disabled) return
|
|
744
|
+
setFocused(true)
|
|
745
|
+
inputRef.current?.focus()
|
|
746
|
+
if (openOnFocus) {
|
|
747
|
+
setOpen(true)
|
|
525
748
|
}
|
|
526
|
-
}
|
|
749
|
+
}
|
|
527
750
|
|
|
528
751
|
useEffect(() => {
|
|
529
752
|
if (highlightedIndex < 0 || !open) return
|
|
@@ -574,50 +797,53 @@ export const Autocomplete = withRef(
|
|
|
574
797
|
if (!multiple || (value as T[]).length === 0) return null
|
|
575
798
|
|
|
576
799
|
const tags = value as T[]
|
|
577
|
-
|
|
578
|
-
|
|
800
|
+
|
|
801
|
+
if (renderSelected) {
|
|
802
|
+
return renderSelected(tags, handleRemoveTag)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const config = customSelectedTags || {}
|
|
806
|
+
const maxItems = config.maxItems
|
|
807
|
+
const visibleTags = maxItems && tags.length > maxItems
|
|
808
|
+
? tags.slice(0, maxItems)
|
|
579
809
|
: tags
|
|
580
|
-
const remainingCount =
|
|
581
|
-
? tags.length -
|
|
810
|
+
const remainingCount = maxItems && tags.length > maxItems
|
|
811
|
+
? tags.length - maxItems
|
|
582
812
|
: 0
|
|
583
813
|
|
|
584
814
|
return (
|
|
585
815
|
<>
|
|
586
|
-
{visibleTags.map(tag =>
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
/>)}
|
|
613
|
-
</Badge>
|
|
614
|
-
)
|
|
615
|
-
})}
|
|
816
|
+
{visibleTags.map(tag => (
|
|
817
|
+
<Badge
|
|
818
|
+
key={renderKey(tag)}
|
|
819
|
+
colorScheme={config.colorScheme}
|
|
820
|
+
colorPalette={config.colorPalette}
|
|
821
|
+
appearance={config.appearance || 'circle'}
|
|
822
|
+
>
|
|
823
|
+
{renderLabel(tag)}
|
|
824
|
+
{!disabled && (
|
|
825
|
+
<IconButton
|
|
826
|
+
icon="Times"
|
|
827
|
+
type="button"
|
|
828
|
+
appearance="none"
|
|
829
|
+
size="xs"
|
|
830
|
+
style={{ color: 'inherit' }}
|
|
831
|
+
onClick={(e) => {
|
|
832
|
+
e.stopPropagation()
|
|
833
|
+
if (!disabled) handleRemoveTag(tag)
|
|
834
|
+
}}
|
|
835
|
+
aria-label={`${t.removeTag} ${renderLabel(tag)}`}
|
|
836
|
+
disabled={disabled}
|
|
837
|
+
tabIndex={0}
|
|
838
|
+
/>
|
|
839
|
+
)}
|
|
840
|
+
</Badge>
|
|
841
|
+
))}
|
|
616
842
|
{remainingCount > 0 && (
|
|
617
843
|
<Badge
|
|
618
|
-
colorScheme={
|
|
619
|
-
colorPalette={
|
|
620
|
-
appearance={
|
|
844
|
+
colorScheme={config.colorScheme}
|
|
845
|
+
colorPalette={config.colorPalette}
|
|
846
|
+
appearance={config.appearance || 'circle'}
|
|
621
847
|
>
|
|
622
848
|
+{remainingCount}
|
|
623
849
|
</Badge>
|
|
@@ -635,7 +861,7 @@ export const Autocomplete = withRef(
|
|
|
635
861
|
<CitricComponent
|
|
636
862
|
tag="div"
|
|
637
863
|
component="autocomplete"
|
|
638
|
-
|
|
864
|
+
colorScheme={colorScheme}
|
|
639
865
|
style={maxHeight ? applyCSSVariable(style, 'max-height', `${maxHeight}px`) : style}
|
|
640
866
|
className={listToClass([
|
|
641
867
|
className,
|
|
@@ -649,29 +875,24 @@ export const Autocomplete = withRef(
|
|
|
649
875
|
{...props}
|
|
650
876
|
>
|
|
651
877
|
<header
|
|
652
|
-
onClick={() => {
|
|
653
|
-
if (disabled) return
|
|
654
|
-
setFocused(true)
|
|
655
|
-
setOpen(true)
|
|
656
|
-
if (inputRef.current) {
|
|
657
|
-
inputRef.current.focus()
|
|
658
|
-
}
|
|
659
|
-
}}
|
|
660
|
-
onFocus={() => setFocused(true)}
|
|
661
878
|
tabIndex={disabled ? undefined : 0}
|
|
879
|
+
onClick={handleFocusAndOpen}
|
|
880
|
+
onFocus={handleFocusAndOpen}
|
|
881
|
+
onKeyDown={handleKeyDown}
|
|
662
882
|
>
|
|
663
883
|
<Row gap="4px" className="input-container">
|
|
664
884
|
{multiple && renderTags()}
|
|
665
885
|
<input
|
|
666
886
|
ref={inputRef}
|
|
887
|
+
id={id}
|
|
667
888
|
type="text"
|
|
668
889
|
value={inputValue}
|
|
669
890
|
onChange={(e) => handleInputChange(e.target.value)}
|
|
670
891
|
onFocus={handleFocus}
|
|
671
892
|
onBlur={handleBlur}
|
|
672
|
-
onKeyDown={handleKeyDown}
|
|
673
893
|
disabled={disabled}
|
|
674
|
-
placeholder={multiple && (value as T[]).length > 0 ? '' : placeholder}
|
|
894
|
+
placeholder={(multiple && (value as T[]).length > 0) ? '' : placeholder}
|
|
895
|
+
tabIndex={disabled ? undefined : 0}
|
|
675
896
|
autoComplete="off"
|
|
676
897
|
aria-autocomplete="list"
|
|
677
898
|
aria-expanded={open}
|
|
@@ -688,11 +909,19 @@ export const Autocomplete = withRef(
|
|
|
688
909
|
type="button"
|
|
689
910
|
onClick={(e) => {
|
|
690
911
|
e.stopPropagation()
|
|
912
|
+
e.preventDefault()
|
|
691
913
|
handleClear()
|
|
692
914
|
}}
|
|
915
|
+
onMouseDown={(e) => {
|
|
916
|
+
e.stopPropagation()
|
|
917
|
+
e.preventDefault()
|
|
918
|
+
}}
|
|
919
|
+
onFocus={(e) => {
|
|
920
|
+
e.stopPropagation()
|
|
921
|
+
}}
|
|
693
922
|
disabled={disabled}
|
|
694
923
|
aria-label={t.clear}
|
|
695
|
-
tabIndex={
|
|
924
|
+
tabIndex={0}
|
|
696
925
|
style={{ width: '12px', height: '12px' }}
|
|
697
926
|
/>
|
|
698
927
|
)}
|
|
@@ -703,11 +932,19 @@ export const Autocomplete = withRef(
|
|
|
703
932
|
type="button"
|
|
704
933
|
onClick={(e) => {
|
|
705
934
|
e.stopPropagation()
|
|
935
|
+
e.preventDefault()
|
|
706
936
|
setOpen((prev) => !prev)
|
|
707
937
|
}}
|
|
938
|
+
onMouseDown={(e) => {
|
|
939
|
+
e.stopPropagation()
|
|
940
|
+
e.preventDefault()
|
|
941
|
+
}}
|
|
942
|
+
onFocus={(e) => {
|
|
943
|
+
e.stopPropagation()
|
|
944
|
+
}}
|
|
708
945
|
disabled={disabled}
|
|
709
946
|
aria-label={open ? t.collapse : t.expand}
|
|
710
|
-
tabIndex={
|
|
947
|
+
tabIndex={0}
|
|
711
948
|
style={{ width: '12px', height: '12px' }}
|
|
712
949
|
/>
|
|
713
950
|
</div>
|
|
@@ -719,11 +956,14 @@ export const Autocomplete = withRef(
|
|
|
719
956
|
id="autocomplete-listbox"
|
|
720
957
|
role="listbox"
|
|
721
958
|
aria-hidden={!open}
|
|
959
|
+
onMouseMove={() => {
|
|
960
|
+
isNavigatingWithKeyboard.current = false
|
|
961
|
+
}}
|
|
722
962
|
{...(open ? {} : { inert: 'true' })}
|
|
723
963
|
>
|
|
724
964
|
{loading && !filteredOptions.length ? (
|
|
725
965
|
<div className="message">{loadingText || t.loading}</div>
|
|
726
|
-
) : filteredOptions.length === 0 && !showCreateOption ? (
|
|
966
|
+
) : filteredOptions.length === 0 && !showCreateOption && !freeSolo ? (
|
|
727
967
|
<div className="message">{noOptionsText || t.noOptions}</div>
|
|
728
968
|
) : (
|
|
729
969
|
<div className="options">
|
|
@@ -756,7 +996,11 @@ export const Autocomplete = withRef(
|
|
|
756
996
|
e.preventDefault()
|
|
757
997
|
}}
|
|
758
998
|
onClick={() => handleSelect(option)}
|
|
759
|
-
onMouseEnter={() =>
|
|
999
|
+
onMouseEnter={() => {
|
|
1000
|
+
if (!isNavigatingWithKeyboard.current) {
|
|
1001
|
+
setHighlightedIndex(index)
|
|
1002
|
+
}
|
|
1003
|
+
}}
|
|
760
1004
|
>
|
|
761
1005
|
{multiple && <Checkbox value={isSelected(option)} readOnly />}
|
|
762
1006
|
{renderOption ? renderOption(option) : renderLabel(option)}
|