@react-spectrum/autocomplete 3.0.0-alpha.11 → 3.0.0-alpha.13
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/main.css +1 -1
- package/dist/main.js +294 -278
- package/dist/main.js.map +1 -1
- package/dist/module.js +294 -278
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +28 -28
- package/src/MobileSearchAutocomplete.tsx +65 -43
- package/src/SearchAutocomplete.tsx +52 -48
- package/src/searchautocomplete.css +6 -0
|
@@ -27,7 +27,15 @@ import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
|
|
|
27
27
|
import Magnifier from '@spectrum-icons/ui/Magnifier';
|
|
28
28
|
import {mergeProps, useId} from '@react-aria/utils';
|
|
29
29
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
30
|
-
import React, {
|
|
30
|
+
import React, {
|
|
31
|
+
HTMLAttributes,
|
|
32
|
+
ReactElement,
|
|
33
|
+
ReactNode,
|
|
34
|
+
useCallback,
|
|
35
|
+
useEffect,
|
|
36
|
+
useRef,
|
|
37
|
+
useState
|
|
38
|
+
} from 'react';
|
|
31
39
|
import searchAutocompleteStyles from './searchautocomplete.css';
|
|
32
40
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
33
41
|
import {setInteractionModality, useHover} from '@react-aria/interactions';
|
|
@@ -45,7 +53,7 @@ import {useOverlayTrigger} from '@react-aria/overlays';
|
|
|
45
53
|
import {useProviderProps} from '@react-spectrum/provider';
|
|
46
54
|
import {useSearchAutocomplete} from '@react-aria/autocomplete';
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
function _MobileSearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
49
57
|
props = useProviderProps(props);
|
|
50
58
|
|
|
51
59
|
let {
|
|
@@ -71,7 +79,7 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
71
79
|
defaultSelectedKey: undefined
|
|
72
80
|
});
|
|
73
81
|
|
|
74
|
-
let buttonRef = useRef<
|
|
82
|
+
let buttonRef = useRef<HTMLDivElement>(null);
|
|
75
83
|
let domRef = useFocusableRef(ref, buttonRef);
|
|
76
84
|
let {triggerProps, overlayProps} = useOverlayTrigger({type: 'listbox'}, state, buttonRef);
|
|
77
85
|
|
|
@@ -82,14 +90,12 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
82
90
|
|
|
83
91
|
// Focus the button and show focus ring when clicking on the label
|
|
84
92
|
labelProps.onClick = () => {
|
|
85
|
-
if (!props.isDisabled) {
|
|
93
|
+
if (!props.isDisabled && buttonRef.current) {
|
|
86
94
|
buttonRef.current.focus();
|
|
87
95
|
setInteractionModality('keyboard');
|
|
88
96
|
}
|
|
89
97
|
};
|
|
90
98
|
|
|
91
|
-
let onClose = () => state.commit();
|
|
92
|
-
|
|
93
99
|
return (
|
|
94
100
|
<>
|
|
95
101
|
<Field
|
|
@@ -112,19 +118,22 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
112
118
|
{state.inputValue || props.placeholder || ''}
|
|
113
119
|
</SearchAutocompleteButton>
|
|
114
120
|
</Field>
|
|
115
|
-
<Tray
|
|
121
|
+
<Tray state={state} isFixedHeight {...overlayProps}>
|
|
116
122
|
<SearchAutocompleteTray
|
|
117
123
|
{...props}
|
|
118
|
-
onClose={
|
|
124
|
+
onClose={state.close}
|
|
119
125
|
overlayProps={overlayProps}
|
|
120
126
|
state={state} />
|
|
121
127
|
</Tray>
|
|
122
128
|
</>
|
|
123
129
|
);
|
|
124
|
-
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export let MobileSearchAutocomplete = React.forwardRef(_MobileSearchAutocomplete) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
133
|
+
|
|
125
134
|
|
|
126
135
|
interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
127
|
-
icon?: ReactElement,
|
|
136
|
+
icon?: ReactElement | null,
|
|
128
137
|
isQuiet?: boolean,
|
|
129
138
|
isDisabled?: boolean,
|
|
130
139
|
isReadOnly?: boolean,
|
|
@@ -137,7 +146,9 @@ interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
|
137
146
|
className?: string
|
|
138
147
|
}
|
|
139
148
|
|
|
140
|
-
|
|
149
|
+
// any type is because we don't want to call useObjectRef because this is an internal component and we know
|
|
150
|
+
// we are always passing an object ref
|
|
151
|
+
const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteButton(props: SearchAutocompleteButtonProps, ref: any) {
|
|
141
152
|
let searchIcon = (
|
|
142
153
|
<Magnifier data-testid="searchicon" />
|
|
143
154
|
);
|
|
@@ -175,8 +186,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
175
186
|
let clearButton = (
|
|
176
187
|
<ClearButton
|
|
177
188
|
onPress={(e) => {
|
|
178
|
-
clearInput();
|
|
179
|
-
props
|
|
189
|
+
clearInput?.();
|
|
190
|
+
props?.onPress?.(e);
|
|
180
191
|
}}
|
|
181
192
|
preventFocus
|
|
182
193
|
aria-label={stringFormatter.format('clear')}
|
|
@@ -190,7 +201,6 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
190
201
|
isDisabled={isDisabled} />
|
|
191
202
|
);
|
|
192
203
|
|
|
193
|
-
|
|
194
204
|
let validation = React.cloneElement(validationIcon, {
|
|
195
205
|
UNSAFE_className: classNames(
|
|
196
206
|
textfieldStyles,
|
|
@@ -219,7 +229,7 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
219
229
|
<div
|
|
220
230
|
{...mergeProps(hoverProps, focusProps, buttonProps)}
|
|
221
231
|
aria-haspopup="dialog"
|
|
222
|
-
ref={ref
|
|
232
|
+
ref={ref}
|
|
223
233
|
style={{...style, outline: 'none'}}
|
|
224
234
|
className={
|
|
225
235
|
classNames(
|
|
@@ -228,13 +238,14 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
228
238
|
{
|
|
229
239
|
'spectrum-InputGroup--quiet': isQuiet,
|
|
230
240
|
'is-disabled': isDisabled,
|
|
231
|
-
'spectrum-InputGroup--invalid': validationState === 'invalid',
|
|
241
|
+
'spectrum-InputGroup--invalid': validationState === 'invalid' && !isDisabled,
|
|
232
242
|
'is-hovered': isHovered,
|
|
233
243
|
'is-focused': isFocused,
|
|
234
244
|
'focus-ring': isFocusVisible
|
|
235
245
|
},
|
|
236
246
|
classNames(
|
|
237
247
|
searchAutocompleteStyles,
|
|
248
|
+
'searchautocomplete',
|
|
238
249
|
'mobile-searchautocomplete'
|
|
239
250
|
),
|
|
240
251
|
className
|
|
@@ -246,8 +257,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
246
257
|
textfieldStyles,
|
|
247
258
|
'spectrum-Textfield',
|
|
248
259
|
{
|
|
249
|
-
'spectrum-Textfield--invalid': validationState === 'invalid',
|
|
250
|
-
'spectrum-Textfield--valid': validationState === 'valid',
|
|
260
|
+
'spectrum-Textfield--invalid': validationState === 'invalid' && !isDisabled,
|
|
261
|
+
'spectrum-Textfield--valid': validationState === 'valid' && !isDisabled,
|
|
251
262
|
'spectrum-Textfield--quiet': isQuiet
|
|
252
263
|
},
|
|
253
264
|
classNames(
|
|
@@ -256,8 +267,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
256
267
|
{
|
|
257
268
|
'is-disabled': isDisabled,
|
|
258
269
|
'is-quiet': isQuiet,
|
|
259
|
-
'spectrum-Search--invalid': validationState === 'invalid',
|
|
260
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
270
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
271
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
261
272
|
}
|
|
262
273
|
)
|
|
263
274
|
)
|
|
@@ -273,8 +284,7 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
273
284
|
'is-placeholder': isPlaceholder,
|
|
274
285
|
'is-disabled': isDisabled,
|
|
275
286
|
'is-quiet': isQuiet,
|
|
276
|
-
'is-focused': isFocused
|
|
277
|
-
'focus-ring': isFocusVisible
|
|
287
|
+
'is-focused': isFocused
|
|
278
288
|
},
|
|
279
289
|
classNames(
|
|
280
290
|
searchStyles,
|
|
@@ -298,21 +308,21 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
298
308
|
{children}
|
|
299
309
|
</span>
|
|
300
310
|
</div>
|
|
301
|
-
{validationState ? validation : null}
|
|
311
|
+
{validationState && !isDisabled ? validation : null}
|
|
302
312
|
{(inputValue !== '' || validationState != null) && !isReadOnly && clearButton}
|
|
303
313
|
</div>
|
|
304
314
|
</div>
|
|
305
315
|
);
|
|
306
316
|
});
|
|
307
317
|
|
|
308
|
-
interface SearchAutocompleteTrayProps extends SpectrumSearchAutocompleteProps<
|
|
309
|
-
state: ComboBoxState<
|
|
318
|
+
interface SearchAutocompleteTrayProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
319
|
+
state: ComboBoxState<T>,
|
|
310
320
|
overlayProps: HTMLAttributes<HTMLElement>,
|
|
311
321
|
loadingIndicator?: ReactElement,
|
|
312
322
|
onClose: () => void
|
|
313
323
|
}
|
|
314
324
|
|
|
315
|
-
function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
325
|
+
function SearchAutocompleteTray<T>(props: SearchAutocompleteTrayProps<T>) {
|
|
316
326
|
let searchIcon = (
|
|
317
327
|
<Magnifier data-testid="searchicon" />
|
|
318
328
|
);
|
|
@@ -331,15 +341,15 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
331
341
|
onSubmit
|
|
332
342
|
} = props;
|
|
333
343
|
|
|
334
|
-
let timeout = useRef(null);
|
|
344
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
335
345
|
let [showLoading, setShowLoading] = useState(false);
|
|
336
|
-
let inputRef = useRef<HTMLInputElement>();
|
|
337
|
-
let popoverRef = useRef<HTMLDivElement>();
|
|
338
|
-
let listBoxRef = useRef<HTMLDivElement>();
|
|
346
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
347
|
+
let popoverRef = useRef<HTMLDivElement>(null);
|
|
348
|
+
let listBoxRef = useRef<HTMLDivElement>(null);
|
|
339
349
|
let layout = useListBoxLayout(state);
|
|
340
350
|
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
341
351
|
|
|
342
|
-
let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete(
|
|
352
|
+
let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete<T>(
|
|
343
353
|
{
|
|
344
354
|
...props,
|
|
345
355
|
keyboardDelegate: layout,
|
|
@@ -351,7 +361,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
351
361
|
);
|
|
352
362
|
|
|
353
363
|
React.useEffect(() => {
|
|
354
|
-
|
|
364
|
+
if (inputRef.current) {
|
|
365
|
+
focusSafely(inputRef.current);
|
|
366
|
+
}
|
|
355
367
|
|
|
356
368
|
// When the tray unmounts, set state.isFocused (i.e. the tray input's focus tracker) to false.
|
|
357
369
|
// This is to prevent state.isFocused from being set to true when the tray closes via tapping on the underlay
|
|
@@ -423,7 +435,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
423
435
|
return;
|
|
424
436
|
}
|
|
425
437
|
|
|
426
|
-
popoverRef.current
|
|
438
|
+
if (popoverRef.current) {
|
|
439
|
+
popoverRef.current.focus();
|
|
440
|
+
}
|
|
427
441
|
}, [inputRef, popoverRef, isTouchDown]);
|
|
428
442
|
|
|
429
443
|
let inputValue = inputProps.value;
|
|
@@ -446,8 +460,10 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
446
460
|
} else if (loadingState !== 'filtering') {
|
|
447
461
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
448
462
|
setShowLoading(false);
|
|
449
|
-
|
|
450
|
-
|
|
463
|
+
if (timeout.current !== null) {
|
|
464
|
+
clearTimeout(timeout.current);
|
|
465
|
+
timeout.current = null;
|
|
466
|
+
}
|
|
451
467
|
}
|
|
452
468
|
|
|
453
469
|
lastInputValue.current = inputValue;
|
|
@@ -456,11 +472,17 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
456
472
|
let onKeyDown = (e) => {
|
|
457
473
|
// Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
|
|
458
474
|
if (e.key === 'Enter' && state.selectionManager.focusedKey == null) {
|
|
459
|
-
popoverRef.current
|
|
460
|
-
onClose
|
|
461
|
-
|
|
475
|
+
popoverRef.current?.focus();
|
|
476
|
+
if (onClose) {
|
|
477
|
+
onClose();
|
|
478
|
+
}
|
|
479
|
+
if (onSubmit) {
|
|
480
|
+
onSubmit(inputValue == null ? null : inputValue.toString(), null);
|
|
481
|
+
}
|
|
462
482
|
} else {
|
|
463
|
-
inputProps.onKeyDown
|
|
483
|
+
if (inputProps.onKeyDown) {
|
|
484
|
+
inputProps.onKeyDown(e);
|
|
485
|
+
}
|
|
464
486
|
}
|
|
465
487
|
};
|
|
466
488
|
|
|
@@ -493,9 +515,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
493
515
|
inputRef={inputRef}
|
|
494
516
|
isDisabled={isDisabled}
|
|
495
517
|
isLoading={showLoading && loadingState === 'filtering'}
|
|
496
|
-
loadingIndicator={loadingState != null
|
|
518
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
497
519
|
validationState={validationState}
|
|
498
|
-
wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly
|
|
520
|
+
wrapperChildren={((state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly) ? clearButton : undefined}
|
|
499
521
|
icon={icon}
|
|
500
522
|
UNSAFE_className={
|
|
501
523
|
classNames(
|
|
@@ -504,8 +526,8 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
504
526
|
'spectrum-Textfield',
|
|
505
527
|
'spectrum-Search--loadable',
|
|
506
528
|
{
|
|
507
|
-
'spectrum-Search--invalid': validationState === 'invalid',
|
|
508
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
529
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
530
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
509
531
|
},
|
|
510
532
|
classNames(
|
|
511
533
|
searchAutocompleteStyles,
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import {AriaButtonProps} from '@react-types/button';
|
|
13
13
|
import {classNames, useFocusableRef, useIsMobileDevice, useResizeObserver, useUnwrapDOMRef} from '@react-spectrum/utils';
|
|
14
14
|
import {ClearButton} from '@react-spectrum/button';
|
|
15
|
-
import {DismissButton, useOverlayPosition} from '@react-aria/overlays';
|
|
16
15
|
import {DOMRefValue, FocusableRef} from '@react-types/shared';
|
|
17
16
|
import {Field} from '@react-spectrum/label';
|
|
18
17
|
import {FocusRing} from '@react-aria/focus';
|
|
@@ -21,10 +20,19 @@ import intlMessages from '../intl/*.json';
|
|
|
21
20
|
import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
|
|
22
21
|
import Magnifier from '@spectrum-icons/ui/Magnifier';
|
|
23
22
|
import {MobileSearchAutocomplete} from './MobileSearchAutocomplete';
|
|
24
|
-
import {Placement} from '@react-types/overlays';
|
|
25
23
|
import {Popover} from '@react-spectrum/overlays';
|
|
26
24
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
27
|
-
import React, {
|
|
25
|
+
import React, {
|
|
26
|
+
forwardRef,
|
|
27
|
+
InputHTMLAttributes,
|
|
28
|
+
ReactElement,
|
|
29
|
+
RefObject,
|
|
30
|
+
useCallback,
|
|
31
|
+
useEffect,
|
|
32
|
+
useRef,
|
|
33
|
+
useState
|
|
34
|
+
} from 'react';
|
|
35
|
+
import searchAutocompleteStyles from './searchautocomplete.css';
|
|
28
36
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
29
37
|
import {SpectrumSearchAutocompleteProps} from '@react-types/autocomplete';
|
|
30
38
|
import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
|
|
@@ -53,7 +61,7 @@ function SearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteP
|
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
57
65
|
props = useProviderProps(props);
|
|
58
66
|
|
|
59
67
|
let {
|
|
@@ -68,10 +76,10 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
68
76
|
|
|
69
77
|
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
70
78
|
let isAsync = loadingState != null;
|
|
71
|
-
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>();
|
|
79
|
+
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>(null);
|
|
72
80
|
let unwrappedPopoverRef = useUnwrapDOMRef(popoverRef);
|
|
73
|
-
let listBoxRef = useRef();
|
|
74
|
-
let inputRef = useRef<HTMLInputElement>();
|
|
81
|
+
let listBoxRef = useRef(null);
|
|
82
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
75
83
|
let domRef = useFocusableRef(ref, inputRef);
|
|
76
84
|
|
|
77
85
|
let {contains} = useFilter({sensitivity: 'base'});
|
|
@@ -100,18 +108,8 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
100
108
|
state
|
|
101
109
|
);
|
|
102
110
|
|
|
103
|
-
let {overlayProps, placement, updatePosition} = useOverlayPosition({
|
|
104
|
-
targetRef: inputRef,
|
|
105
|
-
overlayRef: unwrappedPopoverRef,
|
|
106
|
-
scrollRef: listBoxRef,
|
|
107
|
-
placement: `${direction} end` as Placement,
|
|
108
|
-
shouldFlip: shouldFlip,
|
|
109
|
-
isOpen: state.isOpen,
|
|
110
|
-
onClose: state.close
|
|
111
|
-
});
|
|
112
|
-
|
|
113
111
|
// Measure the width of the inputfield to inform the width of the menu (below).
|
|
114
|
-
let [menuWidth, setMenuWidth] = useState(
|
|
112
|
+
let [menuWidth, setMenuWidth] = useState<number>(0);
|
|
115
113
|
let {scale} = useProvider();
|
|
116
114
|
|
|
117
115
|
let onResize = useCallback(() => {
|
|
@@ -128,20 +126,8 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
128
126
|
|
|
129
127
|
useLayoutEffect(onResize, [scale, onResize]);
|
|
130
128
|
|
|
131
|
-
// Update position once the ListBox has rendered. This ensures that
|
|
132
|
-
// it flips properly when it doesn't fit in the available space.
|
|
133
|
-
// TODO: add ResizeObserver to useOverlayPosition so we don't need this.
|
|
134
|
-
useLayoutEffect(() => {
|
|
135
|
-
if (state.isOpen) {
|
|
136
|
-
requestAnimationFrame(() => {
|
|
137
|
-
updatePosition();
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
}, [state.isOpen, updatePosition]);
|
|
141
|
-
|
|
142
129
|
let style = {
|
|
143
|
-
|
|
144
|
-
width: isQuiet ? null : menuWidth,
|
|
130
|
+
width: isQuiet ? undefined : menuWidth,
|
|
145
131
|
minWidth: isQuiet ? `calc(${menuWidth}px + calc(2 * var(--spectrum-dropdown-quiet-offset)))` : menuWidth
|
|
146
132
|
};
|
|
147
133
|
|
|
@@ -157,14 +143,15 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
157
143
|
clearButtonProps={clearButtonProps} />
|
|
158
144
|
</Field>
|
|
159
145
|
<Popover
|
|
160
|
-
|
|
146
|
+
state={state}
|
|
161
147
|
UNSAFE_style={style}
|
|
162
148
|
UNSAFE_className={classNames(styles, 'spectrum-InputGroup-popover', {'spectrum-InputGroup-popover--quiet': isQuiet})}
|
|
163
149
|
ref={popoverRef}
|
|
164
|
-
|
|
150
|
+
triggerRef={inputRef}
|
|
151
|
+
placement={`${direction} end`}
|
|
165
152
|
hideArrow
|
|
166
153
|
isNonModal
|
|
167
|
-
|
|
154
|
+
shouldFlip={shouldFlip}>
|
|
168
155
|
<ListBoxBase
|
|
169
156
|
{...listBoxProps}
|
|
170
157
|
ref={listBoxRef}
|
|
@@ -182,13 +169,15 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
182
169
|
{stringFormatter.format('noResults')}
|
|
183
170
|
</span>
|
|
184
171
|
)} />
|
|
185
|
-
<DismissButton onDismiss={() => state.close()} />
|
|
186
172
|
</Popover>
|
|
187
173
|
</>
|
|
188
174
|
);
|
|
189
|
-
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let SearchAutocompleteBase = React.forwardRef(_SearchAutocompleteBase) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
190
178
|
|
|
191
|
-
|
|
179
|
+
|
|
180
|
+
interface SearchAutocompleteInputProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
192
181
|
inputProps: InputHTMLAttributes<HTMLInputElement>,
|
|
193
182
|
inputRef: RefObject<HTMLInputElement | HTMLTextAreaElement>,
|
|
194
183
|
style?: React.CSSProperties,
|
|
@@ -197,7 +186,9 @@ interface SearchAutocompleteInputProps extends SpectrumSearchAutocompleteProps<u
|
|
|
197
186
|
clearButtonProps: AriaButtonProps
|
|
198
187
|
}
|
|
199
188
|
|
|
200
|
-
|
|
189
|
+
// any type is because we don't want to call useObjectRef because this is an internal component and we know
|
|
190
|
+
// we are always passing an object ref
|
|
191
|
+
function _SearchAutocompleteInput<T>(props: SearchAutocompleteInputProps<T>, ref: any) {
|
|
201
192
|
let searchIcon = (
|
|
202
193
|
<Magnifier data-testid="searchicon" />
|
|
203
194
|
);
|
|
@@ -220,7 +211,7 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
220
211
|
} = props;
|
|
221
212
|
let {hoverProps, isHovered} = useHover({});
|
|
222
213
|
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
223
|
-
let timeout = useRef(null);
|
|
214
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
224
215
|
let [showLoading, setShowLoading] = useState(false);
|
|
225
216
|
|
|
226
217
|
let loadingCircle = (
|
|
@@ -272,8 +263,10 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
272
263
|
} else if (!isLoading) {
|
|
273
264
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
274
265
|
setShowLoading(false);
|
|
275
|
-
|
|
276
|
-
|
|
266
|
+
if (timeout.current != null) {
|
|
267
|
+
clearTimeout(timeout.current);
|
|
268
|
+
timeout.current = null;
|
|
269
|
+
}
|
|
277
270
|
}
|
|
278
271
|
|
|
279
272
|
lastInputValue.current = inputValue;
|
|
@@ -297,9 +290,13 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
297
290
|
{
|
|
298
291
|
'spectrum-InputGroup--quiet': isQuiet,
|
|
299
292
|
'is-disabled': isDisabled,
|
|
300
|
-
'spectrum-InputGroup--invalid': validationState === 'invalid',
|
|
293
|
+
'spectrum-InputGroup--invalid': validationState === 'invalid' && !isDisabled,
|
|
301
294
|
'is-hovered': isHovered
|
|
302
295
|
},
|
|
296
|
+
classNames(
|
|
297
|
+
searchAutocompleteStyles,
|
|
298
|
+
'searchautocomplete'
|
|
299
|
+
),
|
|
303
300
|
className
|
|
304
301
|
)
|
|
305
302
|
}>
|
|
@@ -314,9 +311,13 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
314
311
|
{
|
|
315
312
|
'is-disabled': isDisabled,
|
|
316
313
|
'is-quiet': isQuiet,
|
|
317
|
-
'spectrum-Search--invalid': validationState === 'invalid',
|
|
318
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
319
|
-
}
|
|
314
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
315
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
316
|
+
},
|
|
317
|
+
classNames(
|
|
318
|
+
styles,
|
|
319
|
+
'spectrum-InputGroup-field'
|
|
320
|
+
)
|
|
320
321
|
)
|
|
321
322
|
}
|
|
322
323
|
inputClassName={classNames(searchStyles, 'spectrum-Search-input')}
|
|
@@ -324,13 +325,16 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
324
325
|
isQuiet={isQuiet}
|
|
325
326
|
validationState={validationState}
|
|
326
327
|
isLoading={showLoading && (isOpen || menuTrigger === 'manual' || loadingState === 'loading')}
|
|
327
|
-
loadingIndicator={loadingState != null
|
|
328
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
328
329
|
icon={icon}
|
|
329
|
-
wrapperChildren={(inputValue !== '' && !isReadOnly)
|
|
330
|
+
wrapperChildren={(inputValue !== '' && !isReadOnly) ? clearButton : undefined}
|
|
331
|
+
disableFocusRing />
|
|
330
332
|
</div>
|
|
331
333
|
</FocusRing>
|
|
332
334
|
);
|
|
333
|
-
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let SearchAutocompleteInput = React.forwardRef(_SearchAutocompleteInput) as <T>(props: SearchAutocompleteInputProps<T> & {ref?: any}) => ReactElement;
|
|
334
338
|
|
|
335
339
|
|
|
336
340
|
/**
|
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
font-style: italic;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/* override .spectrum-InputGroup */
|
|
27
|
+
.searchautocomplete.searchautocomplete {
|
|
28
|
+
border-radius: var(--spectrum-search-border-radius);
|
|
29
|
+
--spectrum-focus-ring-border-radius: var(--spectrum-search-border-radius);
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
.mobile-searchautocomplete {
|
|
27
33
|
outline: none;
|
|
28
34
|
}
|