@react-spectrum/autocomplete 3.0.0-alpha.2 → 3.0.0-alpha.20
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/dist/import.mjs +1187 -0
- package/dist/main.css +1 -1
- package/dist/main.js +820 -640
- package/dist/main.js.map +1 -1
- package/dist/module.js +818 -622
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +34 -29
- package/src/MobileSearchAutocomplete.tsx +171 -130
- package/src/SearchAutocomplete.tsx +94 -67
- package/src/index.ts +2 -3
- package/src/searchautocomplete.css +11 -4
|
@@ -13,21 +13,28 @@
|
|
|
13
13
|
import AlertMedium from '@spectrum-icons/ui/AlertMedium';
|
|
14
14
|
import {AriaButtonProps} from '@react-types/button';
|
|
15
15
|
import CheckmarkMedium from '@spectrum-icons/ui/CheckmarkMedium';
|
|
16
|
-
import {classNames} from '@react-spectrum/utils';
|
|
16
|
+
import {classNames, useFocusableRef} from '@react-spectrum/utils';
|
|
17
17
|
import {ClearButton} from '@react-spectrum/button';
|
|
18
18
|
import {ComboBoxState, useComboBoxState} from '@react-stately/combobox';
|
|
19
|
-
import {DismissButton} from '@react-aria/overlays';
|
|
19
|
+
import {DismissButton, useOverlayTrigger} from '@react-aria/overlays';
|
|
20
20
|
import {Field} from '@react-spectrum/label';
|
|
21
21
|
import {FocusableRef, ValidationState} from '@react-types/shared';
|
|
22
|
-
import {
|
|
23
|
-
import {focusSafely} from '@react-aria/focus';
|
|
22
|
+
import {focusSafely, FocusScope, useFocusRing} from '@react-aria/focus';
|
|
24
23
|
// @ts-ignore
|
|
25
24
|
import intlMessages from '../intl/*.json';
|
|
26
25
|
import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
|
|
27
26
|
import Magnifier from '@spectrum-icons/ui/Magnifier';
|
|
28
27
|
import {mergeProps, useId} from '@react-aria/utils';
|
|
29
28
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
30
|
-
import React, {
|
|
29
|
+
import React, {
|
|
30
|
+
HTMLAttributes,
|
|
31
|
+
ReactElement,
|
|
32
|
+
ReactNode,
|
|
33
|
+
useCallback,
|
|
34
|
+
useEffect,
|
|
35
|
+
useRef,
|
|
36
|
+
useState
|
|
37
|
+
} from 'react';
|
|
31
38
|
import searchAutocompleteStyles from './searchautocomplete.css';
|
|
32
39
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
33
40
|
import {setInteractionModality, useHover} from '@react-aria/interactions';
|
|
@@ -38,14 +45,12 @@ import textfieldStyles from '@adobe/spectrum-css-temp/components/textfield/vars.
|
|
|
38
45
|
import {Tray} from '@react-spectrum/overlays';
|
|
39
46
|
import {useButton} from '@react-aria/button';
|
|
40
47
|
import {useDialog} from '@react-aria/dialog';
|
|
41
|
-
import {useFilter,
|
|
42
|
-
import {useFocusableRef} from '@react-spectrum/utils';
|
|
48
|
+
import {useFilter, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
43
49
|
import {useLabel} from '@react-aria/label';
|
|
44
|
-
import {useOverlayTrigger} from '@react-aria/overlays';
|
|
45
50
|
import {useProviderProps} from '@react-spectrum/provider';
|
|
46
51
|
import {useSearchAutocomplete} from '@react-aria/autocomplete';
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
function _MobileSearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
49
54
|
props = useProviderProps(props);
|
|
50
55
|
|
|
51
56
|
let {
|
|
@@ -70,8 +75,8 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
70
75
|
selectedKey: undefined,
|
|
71
76
|
defaultSelectedKey: undefined
|
|
72
77
|
});
|
|
73
|
-
|
|
74
|
-
let buttonRef = useRef<
|
|
78
|
+
|
|
79
|
+
let buttonRef = useRef<HTMLDivElement>(null);
|
|
75
80
|
let domRef = useFocusableRef(ref, buttonRef);
|
|
76
81
|
let {triggerProps, overlayProps} = useOverlayTrigger({type: 'listbox'}, state, buttonRef);
|
|
77
82
|
|
|
@@ -82,14 +87,12 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
82
87
|
|
|
83
88
|
// Focus the button and show focus ring when clicking on the label
|
|
84
89
|
labelProps.onClick = () => {
|
|
85
|
-
if (!props.isDisabled) {
|
|
90
|
+
if (!props.isDisabled && buttonRef.current) {
|
|
86
91
|
buttonRef.current.focus();
|
|
87
92
|
setInteractionModality('keyboard');
|
|
88
93
|
}
|
|
89
94
|
};
|
|
90
95
|
|
|
91
|
-
let onClose = () => state.commit();
|
|
92
|
-
|
|
93
96
|
return (
|
|
94
97
|
<>
|
|
95
98
|
<Field
|
|
@@ -99,7 +102,7 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
99
102
|
ref={domRef}
|
|
100
103
|
includeNecessityIndicatorInAccessibilityName>
|
|
101
104
|
<SearchAutocompleteButton
|
|
102
|
-
{...mergeProps(triggerProps, fieldProps, {autoFocus: props.autoFocus})}
|
|
105
|
+
{...mergeProps(triggerProps, fieldProps, {autoFocus: props.autoFocus, icon: props.icon})}
|
|
103
106
|
ref={buttonRef}
|
|
104
107
|
isQuiet={isQuiet}
|
|
105
108
|
isDisabled={isDisabled}
|
|
@@ -112,18 +115,22 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
112
115
|
{state.inputValue || props.placeholder || ''}
|
|
113
116
|
</SearchAutocompleteButton>
|
|
114
117
|
</Field>
|
|
115
|
-
<Tray
|
|
118
|
+
<Tray state={state} isFixedHeight {...overlayProps}>
|
|
116
119
|
<SearchAutocompleteTray
|
|
117
120
|
{...props}
|
|
118
|
-
onClose={
|
|
121
|
+
onClose={state.close}
|
|
119
122
|
overlayProps={overlayProps}
|
|
120
123
|
state={state} />
|
|
121
124
|
</Tray>
|
|
122
125
|
</>
|
|
123
126
|
);
|
|
124
|
-
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export let MobileSearchAutocomplete = React.forwardRef(_MobileSearchAutocomplete) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
130
|
+
|
|
125
131
|
|
|
126
132
|
interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
133
|
+
icon?: ReactElement | null,
|
|
127
134
|
isQuiet?: boolean,
|
|
128
135
|
isDisabled?: boolean,
|
|
129
136
|
isReadOnly?: boolean,
|
|
@@ -136,8 +143,15 @@ interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
|
136
143
|
className?: string
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
// any type is because we don't want to call useObjectRef because this is an internal component and we know
|
|
147
|
+
// we are always passing an object ref
|
|
148
|
+
const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteButton(props: SearchAutocompleteButtonProps, ref: any) {
|
|
149
|
+
let searchIcon = (
|
|
150
|
+
<Magnifier data-testid="searchicon" />
|
|
151
|
+
);
|
|
152
|
+
|
|
140
153
|
let {
|
|
154
|
+
icon = searchIcon,
|
|
141
155
|
isQuiet,
|
|
142
156
|
isDisabled,
|
|
143
157
|
isReadOnly,
|
|
@@ -149,33 +163,31 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
149
163
|
style,
|
|
150
164
|
className
|
|
151
165
|
} = props;
|
|
152
|
-
let
|
|
166
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
153
167
|
let valueId = useId();
|
|
154
168
|
let invalidId = useId();
|
|
155
169
|
let validationIcon = validationState === 'invalid'
|
|
156
|
-
? <AlertMedium id={invalidId} aria-label={
|
|
170
|
+
? <AlertMedium id={invalidId} aria-label={stringFormatter.format('invalid')} />
|
|
157
171
|
: <CheckmarkMedium />;
|
|
158
172
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
size: 'S'
|
|
169
|
-
});
|
|
173
|
+
if (icon) {
|
|
174
|
+
icon = React.cloneElement(icon, {
|
|
175
|
+
UNSAFE_className: classNames(
|
|
176
|
+
textfieldStyles,
|
|
177
|
+
'spectrum-Textfield-icon'
|
|
178
|
+
),
|
|
179
|
+
size: 'S'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
170
182
|
|
|
171
183
|
let clearButton = (
|
|
172
184
|
<ClearButton
|
|
173
185
|
onPress={(e) => {
|
|
174
|
-
clearInput();
|
|
175
|
-
props
|
|
186
|
+
clearInput?.();
|
|
187
|
+
props?.onPress?.(e);
|
|
176
188
|
}}
|
|
177
189
|
preventFocus
|
|
178
|
-
aria-label={
|
|
190
|
+
aria-label={stringFormatter.format('clear')}
|
|
179
191
|
excludeFromTabOrder
|
|
180
192
|
UNSAFE_className={
|
|
181
193
|
classNames(
|
|
@@ -186,7 +198,6 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
186
198
|
isDisabled={isDisabled} />
|
|
187
199
|
);
|
|
188
200
|
|
|
189
|
-
|
|
190
201
|
let validation = React.cloneElement(validationIcon, {
|
|
191
202
|
UNSAFE_className: classNames(
|
|
192
203
|
textfieldStyles,
|
|
@@ -194,11 +205,16 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
194
205
|
classNames(
|
|
195
206
|
styles,
|
|
196
207
|
'spectrum-InputGroup-input-validationIcon'
|
|
208
|
+
),
|
|
209
|
+
classNames(
|
|
210
|
+
searchStyles,
|
|
211
|
+
'spectrum-Search-validationIcon'
|
|
197
212
|
)
|
|
198
213
|
)
|
|
199
214
|
});
|
|
200
215
|
|
|
201
216
|
let {hoverProps, isHovered} = useHover({});
|
|
217
|
+
let {isFocused, isFocusVisible, focusProps} = useFocusRing();
|
|
202
218
|
let {buttonProps} = useButton({
|
|
203
219
|
...props,
|
|
204
220
|
'aria-labelledby': [
|
|
@@ -211,102 +227,116 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
211
227
|
}, ref);
|
|
212
228
|
|
|
213
229
|
return (
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
<div
|
|
231
|
+
{...mergeProps(hoverProps, focusProps, buttonProps)}
|
|
232
|
+
aria-haspopup="dialog"
|
|
233
|
+
ref={ref}
|
|
234
|
+
style={{...style, outline: 'none'}}
|
|
235
|
+
className={
|
|
236
|
+
classNames(
|
|
237
|
+
styles,
|
|
238
|
+
'spectrum-InputGroup',
|
|
239
|
+
{
|
|
240
|
+
'spectrum-InputGroup--quiet': isQuiet,
|
|
241
|
+
'is-disabled': isDisabled,
|
|
242
|
+
'spectrum-InputGroup--invalid': validationState === 'invalid' && !isDisabled,
|
|
243
|
+
'is-hovered': isHovered,
|
|
244
|
+
'is-focused': isFocused,
|
|
245
|
+
'focus-ring': isFocusVisible
|
|
246
|
+
},
|
|
247
|
+
classNames(
|
|
248
|
+
searchAutocompleteStyles,
|
|
249
|
+
'searchautocomplete',
|
|
250
|
+
'mobile-searchautocomplete'
|
|
251
|
+
),
|
|
252
|
+
className
|
|
253
|
+
)
|
|
254
|
+
}>
|
|
217
255
|
<div
|
|
218
|
-
{...mergeProps(hoverProps, buttonProps)}
|
|
219
|
-
aria-haspopup="dialog"
|
|
220
|
-
ref={ref as RefObject<HTMLDivElement>}
|
|
221
|
-
style={{...style, outline: 'none'}}
|
|
222
256
|
className={
|
|
223
257
|
classNames(
|
|
224
|
-
|
|
225
|
-
'spectrum-
|
|
258
|
+
textfieldStyles,
|
|
259
|
+
'spectrum-Textfield',
|
|
226
260
|
{
|
|
227
|
-
'spectrum-
|
|
228
|
-
'
|
|
229
|
-
'spectrum-
|
|
230
|
-
'is-hovered': isHovered
|
|
261
|
+
'spectrum-Textfield--invalid': validationState === 'invalid' && !isDisabled,
|
|
262
|
+
'spectrum-Textfield--valid': validationState === 'valid' && !isDisabled,
|
|
263
|
+
'spectrum-Textfield--quiet': isQuiet
|
|
231
264
|
},
|
|
232
265
|
classNames(
|
|
233
|
-
|
|
234
|
-
'
|
|
266
|
+
searchStyles,
|
|
267
|
+
'spectrum-Search',
|
|
268
|
+
'spectrum-Search--loadable',
|
|
269
|
+
{
|
|
270
|
+
'is-disabled': isDisabled,
|
|
271
|
+
'is-quiet': isQuiet,
|
|
272
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
273
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
274
|
+
}
|
|
235
275
|
),
|
|
236
|
-
|
|
276
|
+
classNames(
|
|
277
|
+
styles,
|
|
278
|
+
'spectrum-InputGroup-field'
|
|
279
|
+
)
|
|
237
280
|
)
|
|
238
281
|
}>
|
|
239
282
|
<div
|
|
240
283
|
className={
|
|
241
284
|
classNames(
|
|
242
285
|
textfieldStyles,
|
|
243
|
-
'spectrum-Textfield',
|
|
286
|
+
'spectrum-Textfield-input',
|
|
244
287
|
{
|
|
245
|
-
'spectrum-Textfield
|
|
246
|
-
'
|
|
247
|
-
'
|
|
288
|
+
'spectrum-Textfield-inputIcon': !!icon,
|
|
289
|
+
'is-hovered': isHovered,
|
|
290
|
+
'is-placeholder': isPlaceholder,
|
|
291
|
+
'is-disabled': isDisabled,
|
|
292
|
+
'is-quiet': isQuiet,
|
|
293
|
+
'is-focused': isFocused
|
|
248
294
|
},
|
|
249
295
|
classNames(
|
|
250
296
|
searchStyles,
|
|
251
|
-
'spectrum-Search'
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
257
|
-
}
|
|
297
|
+
'spectrum-Search-input'
|
|
298
|
+
),
|
|
299
|
+
classNames(
|
|
300
|
+
searchAutocompleteStyles,
|
|
301
|
+
'mobile-input'
|
|
258
302
|
)
|
|
259
303
|
)
|
|
260
304
|
}>
|
|
261
|
-
|
|
305
|
+
{icon}
|
|
306
|
+
<span
|
|
307
|
+
id={valueId}
|
|
262
308
|
className={
|
|
263
309
|
classNames(
|
|
264
|
-
|
|
265
|
-
'
|
|
266
|
-
'spectrum-Textfield-inputIcon',
|
|
267
|
-
{
|
|
268
|
-
'is-hovered': isHovered,
|
|
269
|
-
'is-placeholder': isPlaceholder,
|
|
270
|
-
'is-disabled': isDisabled,
|
|
271
|
-
'is-quiet': isQuiet
|
|
272
|
-
},
|
|
273
|
-
classNames(
|
|
274
|
-
searchStyles,
|
|
275
|
-
'spectrum-Search-input'
|
|
276
|
-
)
|
|
310
|
+
searchAutocompleteStyles,
|
|
311
|
+
'mobile-value'
|
|
277
312
|
)
|
|
278
313
|
}>
|
|
279
|
-
{
|
|
280
|
-
|
|
281
|
-
id={valueId}
|
|
282
|
-
className={
|
|
283
|
-
classNames(
|
|
284
|
-
searchAutocompleteStyles,
|
|
285
|
-
'mobile-value'
|
|
286
|
-
)
|
|
287
|
-
}>
|
|
288
|
-
{children}
|
|
289
|
-
</span>
|
|
290
|
-
</div>
|
|
291
|
-
{validationState ? validation : null}
|
|
292
|
-
{(inputValue !== '' || validationState != null) && !isReadOnly && clearButton}
|
|
314
|
+
{children}
|
|
315
|
+
</span>
|
|
293
316
|
</div>
|
|
317
|
+
{validationState && !isDisabled ? validation : null}
|
|
318
|
+
{(inputValue !== '' || validationState != null) && !isReadOnly && clearButton}
|
|
294
319
|
</div>
|
|
295
|
-
</
|
|
320
|
+
</div>
|
|
296
321
|
);
|
|
297
322
|
});
|
|
298
323
|
|
|
299
|
-
interface SearchAutocompleteTrayProps extends SpectrumSearchAutocompleteProps<
|
|
300
|
-
state: ComboBoxState<
|
|
324
|
+
interface SearchAutocompleteTrayProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
325
|
+
state: ComboBoxState<T>,
|
|
301
326
|
overlayProps: HTMLAttributes<HTMLElement>,
|
|
302
327
|
loadingIndicator?: ReactElement,
|
|
303
328
|
onClose: () => void
|
|
304
329
|
}
|
|
305
330
|
|
|
306
|
-
function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
331
|
+
function SearchAutocompleteTray<T>(props: SearchAutocompleteTrayProps<T>) {
|
|
332
|
+
let searchIcon = (
|
|
333
|
+
<Magnifier data-testid="searchicon" />
|
|
334
|
+
);
|
|
335
|
+
|
|
307
336
|
let {
|
|
308
337
|
// completionMode = 'suggest',
|
|
309
338
|
state,
|
|
339
|
+
icon = searchIcon,
|
|
310
340
|
isDisabled,
|
|
311
341
|
validationState,
|
|
312
342
|
label,
|
|
@@ -317,15 +347,16 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
317
347
|
onSubmit
|
|
318
348
|
} = props;
|
|
319
349
|
|
|
320
|
-
let timeout = useRef(null);
|
|
350
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
321
351
|
let [showLoading, setShowLoading] = useState(false);
|
|
322
|
-
let inputRef = useRef<HTMLInputElement>();
|
|
323
|
-
let popoverRef = useRef<HTMLDivElement>();
|
|
324
|
-
let listBoxRef = useRef<HTMLDivElement>();
|
|
325
|
-
let
|
|
326
|
-
let
|
|
327
|
-
|
|
328
|
-
|
|
352
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
353
|
+
let popoverRef = useRef<HTMLDivElement>(null);
|
|
354
|
+
let listBoxRef = useRef<HTMLDivElement>(null);
|
|
355
|
+
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
|
|
356
|
+
let layout = useListBoxLayout(state, isLoading);
|
|
357
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
358
|
+
|
|
359
|
+
let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete<T>(
|
|
329
360
|
{
|
|
330
361
|
...props,
|
|
331
362
|
keyboardDelegate: layout,
|
|
@@ -337,7 +368,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
337
368
|
);
|
|
338
369
|
|
|
339
370
|
React.useEffect(() => {
|
|
340
|
-
|
|
371
|
+
if (inputRef.current) {
|
|
372
|
+
focusSafely(inputRef.current);
|
|
373
|
+
}
|
|
341
374
|
|
|
342
375
|
// When the tray unmounts, set state.isFocused (i.e. the tray input's focus tracker) to false.
|
|
343
376
|
// This is to prevent state.isFocused from being set to true when the tray closes via tapping on the underlay
|
|
@@ -366,7 +399,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
366
399
|
<ClearButton
|
|
367
400
|
{...clearButtonProps}
|
|
368
401
|
preventFocus
|
|
369
|
-
aria-label={
|
|
402
|
+
aria-label={stringFormatter.format('clear')}
|
|
370
403
|
excludeFromTabOrder
|
|
371
404
|
UNSAFE_className={
|
|
372
405
|
classNames(
|
|
@@ -379,7 +412,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
379
412
|
|
|
380
413
|
let loadingCircle = (
|
|
381
414
|
<ProgressCircle
|
|
382
|
-
aria-label={
|
|
415
|
+
aria-label={stringFormatter.format('loading')}
|
|
383
416
|
size="S"
|
|
384
417
|
isIndeterminate
|
|
385
418
|
UNSAFE_className={classNames(
|
|
@@ -409,7 +442,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
409
442
|
return;
|
|
410
443
|
}
|
|
411
444
|
|
|
412
|
-
popoverRef.current
|
|
445
|
+
if (popoverRef.current) {
|
|
446
|
+
popoverRef.current.focus();
|
|
447
|
+
}
|
|
413
448
|
}, [inputRef, popoverRef, isTouchDown]);
|
|
414
449
|
|
|
415
450
|
let inputValue = inputProps.value;
|
|
@@ -432,8 +467,10 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
432
467
|
} else if (loadingState !== 'filtering') {
|
|
433
468
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
434
469
|
setShowLoading(false);
|
|
435
|
-
|
|
436
|
-
|
|
470
|
+
if (timeout.current !== null) {
|
|
471
|
+
clearTimeout(timeout.current);
|
|
472
|
+
timeout.current = null;
|
|
473
|
+
}
|
|
437
474
|
}
|
|
438
475
|
|
|
439
476
|
lastInputValue.current = inputValue;
|
|
@@ -442,25 +479,29 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
442
479
|
let onKeyDown = (e) => {
|
|
443
480
|
// Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
|
|
444
481
|
if (e.key === 'Enter' && state.selectionManager.focusedKey == null) {
|
|
445
|
-
popoverRef.current
|
|
446
|
-
onClose
|
|
447
|
-
|
|
482
|
+
popoverRef.current?.focus();
|
|
483
|
+
if (onClose) {
|
|
484
|
+
onClose();
|
|
485
|
+
}
|
|
486
|
+
if (onSubmit) {
|
|
487
|
+
onSubmit(inputValue == null ? null : inputValue.toString(), null);
|
|
488
|
+
}
|
|
448
489
|
} else {
|
|
449
|
-
inputProps.onKeyDown
|
|
490
|
+
if (inputProps.onKeyDown) {
|
|
491
|
+
inputProps.onKeyDown(e);
|
|
492
|
+
}
|
|
450
493
|
}
|
|
451
494
|
};
|
|
452
495
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
size: 'S'
|
|
463
|
-
});
|
|
496
|
+
if (icon) {
|
|
497
|
+
icon = React.cloneElement(icon, {
|
|
498
|
+
UNSAFE_className: classNames(
|
|
499
|
+
textfieldStyles,
|
|
500
|
+
'spectrum-Textfield-icon'
|
|
501
|
+
),
|
|
502
|
+
size: 'S'
|
|
503
|
+
});
|
|
504
|
+
}
|
|
464
505
|
|
|
465
506
|
return (
|
|
466
507
|
<FocusScope restoreFocus contain>
|
|
@@ -481,9 +522,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
481
522
|
inputRef={inputRef}
|
|
482
523
|
isDisabled={isDisabled}
|
|
483
524
|
isLoading={showLoading && loadingState === 'filtering'}
|
|
484
|
-
loadingIndicator={loadingState != null
|
|
525
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
485
526
|
validationState={validationState}
|
|
486
|
-
wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly
|
|
527
|
+
wrapperChildren={((state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly) ? clearButton : undefined}
|
|
487
528
|
icon={icon}
|
|
488
529
|
UNSAFE_className={
|
|
489
530
|
classNames(
|
|
@@ -492,8 +533,8 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
492
533
|
'spectrum-Textfield',
|
|
493
534
|
'spectrum-Search--loadable',
|
|
494
535
|
{
|
|
495
|
-
'spectrum-Search--invalid': validationState === 'invalid',
|
|
496
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
536
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
537
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
497
538
|
},
|
|
498
539
|
classNames(
|
|
499
540
|
searchAutocompleteStyles,
|
|
@@ -527,7 +568,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
527
568
|
shouldUseVirtualFocus
|
|
528
569
|
renderEmptyState={() => loadingState !== 'loading' && (
|
|
529
570
|
<span className={classNames(searchAutocompleteStyles, 'no-results')}>
|
|
530
|
-
{
|
|
571
|
+
{stringFormatter.format('noResults')}
|
|
531
572
|
</span>
|
|
532
573
|
)}
|
|
533
574
|
UNSAFE_className={
|
|
@@ -539,7 +580,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
539
580
|
ref={listBoxRef}
|
|
540
581
|
onScroll={onScroll}
|
|
541
582
|
onLoadMore={onLoadMore}
|
|
542
|
-
isLoading={
|
|
583
|
+
isLoading={isLoading} />
|
|
543
584
|
<DismissButton onDismiss={onClose} />
|
|
544
585
|
</div>
|
|
545
586
|
</FocusScope>
|