@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.
@@ -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, {HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useEffect, useRef, useState} from '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
- export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
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<HTMLElement>();
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
- const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteButton(props: SearchAutocompleteButtonProps, ref: RefObject<HTMLElement>) {
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.onPress(e);
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 as RefObject<HTMLDivElement>}
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<unknown> {
307
- state: ComboBoxState<unknown>,
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
- focusSafely(inputRef.current);
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.focus();
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
- clearTimeout(timeout.current);
448
- timeout.current = null;
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.focus();
458
- onClose();
459
- onSubmit(inputValue.toString(), null);
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(e);
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 && loadingCircle}
518
+ loadingIndicator={loadingState != null ? loadingCircle : undefined}
495
519
  validationState={validationState}
496
- wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly && clearButton}
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, {forwardRef, InputHTMLAttributes, RefObject, useCallback, useEffect, useRef, useState} from '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
- const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
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(null);
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 ? null : menuWidth,
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
- interface SearchAutocompleteInputProps extends SpectrumSearchAutocompleteProps<unknown> {
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
- const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInput(props: SearchAutocompleteInputProps, ref: RefObject<HTMLElement>) {
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
- clearTimeout(timeout.current);
253
- timeout.current = null;
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 && loadingCircle}
328
+ loadingIndicator={loadingState != null ? loadingCircle : undefined}
313
329
  icon={icon}
314
- wrapperChildren={(inputValue !== '' && !isReadOnly) && clearButton}
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
  /**