@react-spectrum/autocomplete 3.0.0-alpha.12 → 3.0.0-alpha.14
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 +270 -234
- package/dist/main.js.map +1 -1
- package/dist/module.js +270 -234
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/MobileSearchAutocomplete.tsx +53 -29
- package/src/SearchAutocomplete.tsx +34 -16
|
@@ -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,7 +90,7 @@ 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
|
}
|
|
@@ -119,10 +127,13 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
119
127
|
</Tray>
|
|
120
128
|
</>
|
|
121
129
|
);
|
|
122
|
-
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export let MobileSearchAutocomplete = React.forwardRef(_MobileSearchAutocomplete) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
133
|
+
|
|
123
134
|
|
|
124
135
|
interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
125
|
-
icon?: ReactElement,
|
|
136
|
+
icon?: ReactElement | null,
|
|
126
137
|
isQuiet?: boolean,
|
|
127
138
|
isDisabled?: boolean,
|
|
128
139
|
isReadOnly?: boolean,
|
|
@@ -135,7 +146,9 @@ interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
|
135
146
|
className?: string
|
|
136
147
|
}
|
|
137
148
|
|
|
138
|
-
|
|
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) {
|
|
139
152
|
let searchIcon = (
|
|
140
153
|
<Magnifier data-testid="searchicon" />
|
|
141
154
|
);
|
|
@@ -173,8 +186,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
173
186
|
let clearButton = (
|
|
174
187
|
<ClearButton
|
|
175
188
|
onPress={(e) => {
|
|
176
|
-
clearInput();
|
|
177
|
-
props
|
|
189
|
+
clearInput?.();
|
|
190
|
+
props?.onPress?.(e);
|
|
178
191
|
}}
|
|
179
192
|
preventFocus
|
|
180
193
|
aria-label={stringFormatter.format('clear')}
|
|
@@ -188,7 +201,6 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
188
201
|
isDisabled={isDisabled} />
|
|
189
202
|
);
|
|
190
203
|
|
|
191
|
-
|
|
192
204
|
let validation = React.cloneElement(validationIcon, {
|
|
193
205
|
UNSAFE_className: classNames(
|
|
194
206
|
textfieldStyles,
|
|
@@ -217,7 +229,7 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
217
229
|
<div
|
|
218
230
|
{...mergeProps(hoverProps, focusProps, buttonProps)}
|
|
219
231
|
aria-haspopup="dialog"
|
|
220
|
-
ref={ref
|
|
232
|
+
ref={ref}
|
|
221
233
|
style={{...style, outline: 'none'}}
|
|
222
234
|
className={
|
|
223
235
|
classNames(
|
|
@@ -303,14 +315,14 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
303
315
|
);
|
|
304
316
|
});
|
|
305
317
|
|
|
306
|
-
interface SearchAutocompleteTrayProps extends SpectrumSearchAutocompleteProps<
|
|
307
|
-
state: ComboBoxState<
|
|
318
|
+
interface SearchAutocompleteTrayProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
319
|
+
state: ComboBoxState<T>,
|
|
308
320
|
overlayProps: HTMLAttributes<HTMLElement>,
|
|
309
321
|
loadingIndicator?: ReactElement,
|
|
310
322
|
onClose: () => void
|
|
311
323
|
}
|
|
312
324
|
|
|
313
|
-
function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
325
|
+
function SearchAutocompleteTray<T>(props: SearchAutocompleteTrayProps<T>) {
|
|
314
326
|
let searchIcon = (
|
|
315
327
|
<Magnifier data-testid="searchicon" />
|
|
316
328
|
);
|
|
@@ -329,15 +341,15 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
329
341
|
onSubmit
|
|
330
342
|
} = props;
|
|
331
343
|
|
|
332
|
-
let timeout = useRef(null);
|
|
344
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
333
345
|
let [showLoading, setShowLoading] = useState(false);
|
|
334
|
-
let inputRef = useRef<HTMLInputElement>();
|
|
335
|
-
let popoverRef = useRef<HTMLDivElement>();
|
|
336
|
-
let listBoxRef = useRef<HTMLDivElement>();
|
|
346
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
347
|
+
let popoverRef = useRef<HTMLDivElement>(null);
|
|
348
|
+
let listBoxRef = useRef<HTMLDivElement>(null);
|
|
337
349
|
let layout = useListBoxLayout(state);
|
|
338
350
|
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
339
351
|
|
|
340
|
-
let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete(
|
|
352
|
+
let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete<T>(
|
|
341
353
|
{
|
|
342
354
|
...props,
|
|
343
355
|
keyboardDelegate: layout,
|
|
@@ -349,7 +361,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
349
361
|
);
|
|
350
362
|
|
|
351
363
|
React.useEffect(() => {
|
|
352
|
-
|
|
364
|
+
if (inputRef.current) {
|
|
365
|
+
focusSafely(inputRef.current);
|
|
366
|
+
}
|
|
353
367
|
|
|
354
368
|
// When the tray unmounts, set state.isFocused (i.e. the tray input's focus tracker) to false.
|
|
355
369
|
// This is to prevent state.isFocused from being set to true when the tray closes via tapping on the underlay
|
|
@@ -421,7 +435,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
421
435
|
return;
|
|
422
436
|
}
|
|
423
437
|
|
|
424
|
-
popoverRef.current
|
|
438
|
+
if (popoverRef.current) {
|
|
439
|
+
popoverRef.current.focus();
|
|
440
|
+
}
|
|
425
441
|
}, [inputRef, popoverRef, isTouchDown]);
|
|
426
442
|
|
|
427
443
|
let inputValue = inputProps.value;
|
|
@@ -444,8 +460,10 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
444
460
|
} else if (loadingState !== 'filtering') {
|
|
445
461
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
446
462
|
setShowLoading(false);
|
|
447
|
-
|
|
448
|
-
|
|
463
|
+
if (timeout.current !== null) {
|
|
464
|
+
clearTimeout(timeout.current);
|
|
465
|
+
timeout.current = null;
|
|
466
|
+
}
|
|
449
467
|
}
|
|
450
468
|
|
|
451
469
|
lastInputValue.current = inputValue;
|
|
@@ -454,11 +472,17 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
454
472
|
let onKeyDown = (e) => {
|
|
455
473
|
// Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
|
|
456
474
|
if (e.key === 'Enter' && state.selectionManager.focusedKey == null) {
|
|
457
|
-
popoverRef.current
|
|
458
|
-
onClose
|
|
459
|
-
|
|
475
|
+
popoverRef.current?.focus();
|
|
476
|
+
if (onClose) {
|
|
477
|
+
onClose();
|
|
478
|
+
}
|
|
479
|
+
if (onSubmit) {
|
|
480
|
+
onSubmit(inputValue == null ? null : inputValue.toString(), null);
|
|
481
|
+
}
|
|
460
482
|
} else {
|
|
461
|
-
inputProps.onKeyDown
|
|
483
|
+
if (inputProps.onKeyDown) {
|
|
484
|
+
inputProps.onKeyDown(e);
|
|
485
|
+
}
|
|
462
486
|
}
|
|
463
487
|
};
|
|
464
488
|
|
|
@@ -491,9 +515,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
491
515
|
inputRef={inputRef}
|
|
492
516
|
isDisabled={isDisabled}
|
|
493
517
|
isLoading={showLoading && loadingState === 'filtering'}
|
|
494
|
-
loadingIndicator={loadingState != null
|
|
518
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
495
519
|
validationState={validationState}
|
|
496
|
-
wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly
|
|
520
|
+
wrapperChildren={((state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly) ? clearButton : undefined}
|
|
497
521
|
icon={icon}
|
|
498
522
|
UNSAFE_className={
|
|
499
523
|
classNames(
|
|
@@ -22,7 +22,16 @@ import Magnifier from '@spectrum-icons/ui/Magnifier';
|
|
|
22
22
|
import {MobileSearchAutocomplete} from './MobileSearchAutocomplete';
|
|
23
23
|
import {Popover} from '@react-spectrum/overlays';
|
|
24
24
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
25
|
-
import React, {
|
|
25
|
+
import React, {
|
|
26
|
+
forwardRef,
|
|
27
|
+
InputHTMLAttributes,
|
|
28
|
+
ReactElement,
|
|
29
|
+
RefObject,
|
|
30
|
+
useCallback,
|
|
31
|
+
useEffect,
|
|
32
|
+
useRef,
|
|
33
|
+
useState
|
|
34
|
+
} from 'react';
|
|
26
35
|
import searchAutocompleteStyles from './searchautocomplete.css';
|
|
27
36
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
28
37
|
import {SpectrumSearchAutocompleteProps} from '@react-types/autocomplete';
|
|
@@ -52,7 +61,7 @@ function SearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteP
|
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
56
65
|
props = useProviderProps(props);
|
|
57
66
|
|
|
58
67
|
let {
|
|
@@ -67,10 +76,10 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
67
76
|
|
|
68
77
|
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
69
78
|
let isAsync = loadingState != null;
|
|
70
|
-
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>();
|
|
79
|
+
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>(null);
|
|
71
80
|
let unwrappedPopoverRef = useUnwrapDOMRef(popoverRef);
|
|
72
|
-
let listBoxRef = useRef();
|
|
73
|
-
let inputRef = useRef<HTMLInputElement>();
|
|
81
|
+
let listBoxRef = useRef(null);
|
|
82
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
74
83
|
let domRef = useFocusableRef(ref, inputRef);
|
|
75
84
|
|
|
76
85
|
let {contains} = useFilter({sensitivity: 'base'});
|
|
@@ -100,7 +109,7 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
100
109
|
);
|
|
101
110
|
|
|
102
111
|
// Measure the width of the inputfield to inform the width of the menu (below).
|
|
103
|
-
let [menuWidth, setMenuWidth] = useState(
|
|
112
|
+
let [menuWidth, setMenuWidth] = useState<number>(0);
|
|
104
113
|
let {scale} = useProvider();
|
|
105
114
|
|
|
106
115
|
let onResize = useCallback(() => {
|
|
@@ -118,7 +127,7 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
118
127
|
useLayoutEffect(onResize, [scale, onResize]);
|
|
119
128
|
|
|
120
129
|
let style = {
|
|
121
|
-
width: isQuiet ?
|
|
130
|
+
width: isQuiet ? undefined : menuWidth,
|
|
122
131
|
minWidth: isQuiet ? `calc(${menuWidth}px + calc(2 * var(--spectrum-dropdown-quiet-offset)))` : menuWidth
|
|
123
132
|
};
|
|
124
133
|
|
|
@@ -163,9 +172,12 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
163
172
|
</Popover>
|
|
164
173
|
</>
|
|
165
174
|
);
|
|
166
|
-
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let SearchAutocompleteBase = React.forwardRef(_SearchAutocompleteBase) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
167
178
|
|
|
168
|
-
|
|
179
|
+
|
|
180
|
+
interface SearchAutocompleteInputProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
169
181
|
inputProps: InputHTMLAttributes<HTMLInputElement>,
|
|
170
182
|
inputRef: RefObject<HTMLInputElement | HTMLTextAreaElement>,
|
|
171
183
|
style?: React.CSSProperties,
|
|
@@ -174,7 +186,9 @@ interface SearchAutocompleteInputProps extends SpectrumSearchAutocompleteProps<u
|
|
|
174
186
|
clearButtonProps: AriaButtonProps
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
|
|
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) {
|
|
178
192
|
let searchIcon = (
|
|
179
193
|
<Magnifier data-testid="searchicon" />
|
|
180
194
|
);
|
|
@@ -197,7 +211,7 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
197
211
|
} = props;
|
|
198
212
|
let {hoverProps, isHovered} = useHover({});
|
|
199
213
|
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
200
|
-
let timeout = useRef(null);
|
|
214
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
201
215
|
let [showLoading, setShowLoading] = useState(false);
|
|
202
216
|
|
|
203
217
|
let loadingCircle = (
|
|
@@ -249,8 +263,10 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
249
263
|
} else if (!isLoading) {
|
|
250
264
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
251
265
|
setShowLoading(false);
|
|
252
|
-
|
|
253
|
-
|
|
266
|
+
if (timeout.current != null) {
|
|
267
|
+
clearTimeout(timeout.current);
|
|
268
|
+
timeout.current = null;
|
|
269
|
+
}
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
lastInputValue.current = inputValue;
|
|
@@ -309,14 +325,16 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
309
325
|
isQuiet={isQuiet}
|
|
310
326
|
validationState={validationState}
|
|
311
327
|
isLoading={showLoading && (isOpen || menuTrigger === 'manual' || loadingState === 'loading')}
|
|
312
|
-
loadingIndicator={loadingState != null
|
|
328
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
313
329
|
icon={icon}
|
|
314
|
-
wrapperChildren={(inputValue !== '' && !isReadOnly)
|
|
330
|
+
wrapperChildren={(inputValue !== '' && !isReadOnly) ? clearButton : undefined}
|
|
315
331
|
disableFocusRing />
|
|
316
332
|
</div>
|
|
317
333
|
</FocusRing>
|
|
318
334
|
);
|
|
319
|
-
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let SearchAutocompleteInput = React.forwardRef(_SearchAutocompleteInput) as <T>(props: SearchAutocompleteInputProps<T> & {ref?: any}) => ReactElement;
|
|
320
338
|
|
|
321
339
|
|
|
322
340
|
/**
|