@react-spectrum/autocomplete 3.0.0-alpha.53 → 3.0.0-alpha.55

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.
Files changed (226) hide show
  1. package/dist/import.mjs +5 -3
  2. package/dist/main.js +7 -5
  3. package/dist/main.js.map +1 -1
  4. package/dist/module.js +5 -3
  5. package/dist/module.js.map +1 -1
  6. package/dist/types/src/index.d.ts +4 -0
  7. package/package.json +24 -55
  8. package/src/index.ts +5 -3
  9. package/dist/MobileSearchAutocomplete.main.js +0 -449
  10. package/dist/MobileSearchAutocomplete.main.js.map +0 -1
  11. package/dist/MobileSearchAutocomplete.mjs +0 -444
  12. package/dist/MobileSearchAutocomplete.module.js +0 -444
  13. package/dist/MobileSearchAutocomplete.module.js.map +0 -1
  14. package/dist/SearchAutocomplete.main.js +0 -305
  15. package/dist/SearchAutocomplete.main.js.map +0 -1
  16. package/dist/SearchAutocomplete.mjs +0 -300
  17. package/dist/SearchAutocomplete.module.js +0 -300
  18. package/dist/SearchAutocomplete.module.js.map +0 -1
  19. package/dist/ar-AE.main.js +0 -9
  20. package/dist/ar-AE.main.js.map +0 -1
  21. package/dist/ar-AE.mjs +0 -11
  22. package/dist/ar-AE.module.js +0 -11
  23. package/dist/ar-AE.module.js.map +0 -1
  24. package/dist/autocomplete.02db68b2.css +0 -55
  25. package/dist/autocomplete.02db68b2.css.map +0 -1
  26. package/dist/autocomplete.102c5c14.css +0 -223
  27. package/dist/autocomplete.102c5c14.css.map +0 -1
  28. package/dist/autocomplete.30e64bfb.css +0 -550
  29. package/dist/autocomplete.30e64bfb.css.map +0 -1
  30. package/dist/autocomplete.c4adfff6.css +0 -649
  31. package/dist/autocomplete.c4adfff6.css.map +0 -1
  32. package/dist/bg-BG.main.js +0 -9
  33. package/dist/bg-BG.main.js.map +0 -1
  34. package/dist/bg-BG.mjs +0 -11
  35. package/dist/bg-BG.module.js +0 -11
  36. package/dist/bg-BG.module.js.map +0 -1
  37. package/dist/cs-CZ.main.js +0 -9
  38. package/dist/cs-CZ.main.js.map +0 -1
  39. package/dist/cs-CZ.mjs +0 -11
  40. package/dist/cs-CZ.module.js +0 -11
  41. package/dist/cs-CZ.module.js.map +0 -1
  42. package/dist/da-DK.main.js +0 -9
  43. package/dist/da-DK.main.js.map +0 -1
  44. package/dist/da-DK.mjs +0 -11
  45. package/dist/da-DK.module.js +0 -11
  46. package/dist/da-DK.module.js.map +0 -1
  47. package/dist/de-DE.main.js +0 -9
  48. package/dist/de-DE.main.js.map +0 -1
  49. package/dist/de-DE.mjs +0 -11
  50. package/dist/de-DE.module.js +0 -11
  51. package/dist/de-DE.module.js.map +0 -1
  52. package/dist/el-GR.main.js +0 -9
  53. package/dist/el-GR.main.js.map +0 -1
  54. package/dist/el-GR.mjs +0 -11
  55. package/dist/el-GR.module.js +0 -11
  56. package/dist/el-GR.module.js.map +0 -1
  57. package/dist/en-US.main.js +0 -9
  58. package/dist/en-US.main.js.map +0 -1
  59. package/dist/en-US.mjs +0 -11
  60. package/dist/en-US.module.js +0 -11
  61. package/dist/en-US.module.js.map +0 -1
  62. package/dist/es-ES.main.js +0 -9
  63. package/dist/es-ES.main.js.map +0 -1
  64. package/dist/es-ES.mjs +0 -11
  65. package/dist/es-ES.module.js +0 -11
  66. package/dist/es-ES.module.js.map +0 -1
  67. package/dist/et-EE.main.js +0 -9
  68. package/dist/et-EE.main.js.map +0 -1
  69. package/dist/et-EE.mjs +0 -11
  70. package/dist/et-EE.module.js +0 -11
  71. package/dist/et-EE.module.js.map +0 -1
  72. package/dist/fi-FI.main.js +0 -9
  73. package/dist/fi-FI.main.js.map +0 -1
  74. package/dist/fi-FI.mjs +0 -11
  75. package/dist/fi-FI.module.js +0 -11
  76. package/dist/fi-FI.module.js.map +0 -1
  77. package/dist/fr-FR.main.js +0 -9
  78. package/dist/fr-FR.main.js.map +0 -1
  79. package/dist/fr-FR.mjs +0 -11
  80. package/dist/fr-FR.module.js +0 -11
  81. package/dist/fr-FR.module.js.map +0 -1
  82. package/dist/he-IL.main.js +0 -9
  83. package/dist/he-IL.main.js.map +0 -1
  84. package/dist/he-IL.mjs +0 -11
  85. package/dist/he-IL.module.js +0 -11
  86. package/dist/he-IL.module.js.map +0 -1
  87. package/dist/hr-HR.main.js +0 -9
  88. package/dist/hr-HR.main.js.map +0 -1
  89. package/dist/hr-HR.mjs +0 -11
  90. package/dist/hr-HR.module.js +0 -11
  91. package/dist/hr-HR.module.js.map +0 -1
  92. package/dist/hu-HU.main.js +0 -9
  93. package/dist/hu-HU.main.js.map +0 -1
  94. package/dist/hu-HU.mjs +0 -11
  95. package/dist/hu-HU.module.js +0 -11
  96. package/dist/hu-HU.module.js.map +0 -1
  97. package/dist/inputgroup_vars_css.main.js +0 -86
  98. package/dist/inputgroup_vars_css.main.js.map +0 -1
  99. package/dist/inputgroup_vars_css.mjs +0 -88
  100. package/dist/inputgroup_vars_css.module.js +0 -88
  101. package/dist/inputgroup_vars_css.module.js.map +0 -1
  102. package/dist/intlStrings.main.js +0 -108
  103. package/dist/intlStrings.main.js.map +0 -1
  104. package/dist/intlStrings.mjs +0 -110
  105. package/dist/intlStrings.module.js +0 -110
  106. package/dist/intlStrings.module.js.map +0 -1
  107. package/dist/it-IT.main.js +0 -9
  108. package/dist/it-IT.main.js.map +0 -1
  109. package/dist/it-IT.mjs +0 -11
  110. package/dist/it-IT.module.js +0 -11
  111. package/dist/it-IT.module.js.map +0 -1
  112. package/dist/ja-JP.main.js +0 -9
  113. package/dist/ja-JP.main.js.map +0 -1
  114. package/dist/ja-JP.mjs +0 -11
  115. package/dist/ja-JP.module.js +0 -11
  116. package/dist/ja-JP.module.js.map +0 -1
  117. package/dist/ko-KR.main.js +0 -9
  118. package/dist/ko-KR.main.js.map +0 -1
  119. package/dist/ko-KR.mjs +0 -11
  120. package/dist/ko-KR.module.js +0 -11
  121. package/dist/ko-KR.module.js.map +0 -1
  122. package/dist/lt-LT.main.js +0 -9
  123. package/dist/lt-LT.main.js.map +0 -1
  124. package/dist/lt-LT.mjs +0 -11
  125. package/dist/lt-LT.module.js +0 -11
  126. package/dist/lt-LT.module.js.map +0 -1
  127. package/dist/lv-LV.main.js +0 -9
  128. package/dist/lv-LV.main.js.map +0 -1
  129. package/dist/lv-LV.mjs +0 -11
  130. package/dist/lv-LV.module.js +0 -11
  131. package/dist/lv-LV.module.js.map +0 -1
  132. package/dist/nb-NO.main.js +0 -9
  133. package/dist/nb-NO.main.js.map +0 -1
  134. package/dist/nb-NO.mjs +0 -11
  135. package/dist/nb-NO.module.js +0 -11
  136. package/dist/nb-NO.module.js.map +0 -1
  137. package/dist/nl-NL.main.js +0 -9
  138. package/dist/nl-NL.main.js.map +0 -1
  139. package/dist/nl-NL.mjs +0 -11
  140. package/dist/nl-NL.module.js +0 -11
  141. package/dist/nl-NL.module.js.map +0 -1
  142. package/dist/pl-PL.main.js +0 -9
  143. package/dist/pl-PL.main.js.map +0 -1
  144. package/dist/pl-PL.mjs +0 -11
  145. package/dist/pl-PL.module.js +0 -11
  146. package/dist/pl-PL.module.js.map +0 -1
  147. package/dist/pt-BR.main.js +0 -9
  148. package/dist/pt-BR.main.js.map +0 -1
  149. package/dist/pt-BR.mjs +0 -11
  150. package/dist/pt-BR.module.js +0 -11
  151. package/dist/pt-BR.module.js.map +0 -1
  152. package/dist/pt-PT.main.js +0 -9
  153. package/dist/pt-PT.main.js.map +0 -1
  154. package/dist/pt-PT.mjs +0 -11
  155. package/dist/pt-PT.module.js +0 -11
  156. package/dist/pt-PT.module.js.map +0 -1
  157. package/dist/ro-RO.main.js +0 -9
  158. package/dist/ro-RO.main.js.map +0 -1
  159. package/dist/ro-RO.mjs +0 -11
  160. package/dist/ro-RO.module.js +0 -11
  161. package/dist/ro-RO.module.js.map +0 -1
  162. package/dist/ru-RU.main.js +0 -9
  163. package/dist/ru-RU.main.js.map +0 -1
  164. package/dist/ru-RU.mjs +0 -11
  165. package/dist/ru-RU.module.js +0 -11
  166. package/dist/ru-RU.module.js.map +0 -1
  167. package/dist/search_vars_css.main.js +0 -53
  168. package/dist/search_vars_css.main.js.map +0 -1
  169. package/dist/search_vars_css.mjs +0 -55
  170. package/dist/search_vars_css.module.js +0 -55
  171. package/dist/search_vars_css.module.js.map +0 -1
  172. package/dist/searchautocomplete_css.main.js +0 -38
  173. package/dist/searchautocomplete_css.main.js.map +0 -1
  174. package/dist/searchautocomplete_css.mjs +0 -40
  175. package/dist/searchautocomplete_css.module.js +0 -40
  176. package/dist/searchautocomplete_css.module.js.map +0 -1
  177. package/dist/sk-SK.main.js +0 -9
  178. package/dist/sk-SK.main.js.map +0 -1
  179. package/dist/sk-SK.mjs +0 -11
  180. package/dist/sk-SK.module.js +0 -11
  181. package/dist/sk-SK.module.js.map +0 -1
  182. package/dist/sl-SI.main.js +0 -9
  183. package/dist/sl-SI.main.js.map +0 -1
  184. package/dist/sl-SI.mjs +0 -11
  185. package/dist/sl-SI.module.js +0 -11
  186. package/dist/sl-SI.module.js.map +0 -1
  187. package/dist/sr-SP.main.js +0 -9
  188. package/dist/sr-SP.main.js.map +0 -1
  189. package/dist/sr-SP.mjs +0 -11
  190. package/dist/sr-SP.module.js +0 -11
  191. package/dist/sr-SP.module.js.map +0 -1
  192. package/dist/sv-SE.main.js +0 -9
  193. package/dist/sv-SE.main.js.map +0 -1
  194. package/dist/sv-SE.mjs +0 -11
  195. package/dist/sv-SE.module.js +0 -11
  196. package/dist/sv-SE.module.js.map +0 -1
  197. package/dist/textfield_vars_css.main.js +0 -74
  198. package/dist/textfield_vars_css.main.js.map +0 -1
  199. package/dist/textfield_vars_css.mjs +0 -76
  200. package/dist/textfield_vars_css.module.js +0 -76
  201. package/dist/textfield_vars_css.module.js.map +0 -1
  202. package/dist/tr-TR.main.js +0 -9
  203. package/dist/tr-TR.main.js.map +0 -1
  204. package/dist/tr-TR.mjs +0 -11
  205. package/dist/tr-TR.module.js +0 -11
  206. package/dist/tr-TR.module.js.map +0 -1
  207. package/dist/types.d.ts +0 -13
  208. package/dist/types.d.ts.map +0 -1
  209. package/dist/uk-UA.main.js +0 -9
  210. package/dist/uk-UA.main.js.map +0 -1
  211. package/dist/uk-UA.mjs +0 -11
  212. package/dist/uk-UA.module.js +0 -11
  213. package/dist/uk-UA.module.js.map +0 -1
  214. package/dist/zh-CN.main.js +0 -9
  215. package/dist/zh-CN.main.js.map +0 -1
  216. package/dist/zh-CN.mjs +0 -11
  217. package/dist/zh-CN.module.js +0 -11
  218. package/dist/zh-CN.module.js.map +0 -1
  219. package/dist/zh-TW.main.js +0 -9
  220. package/dist/zh-TW.main.js.map +0 -1
  221. package/dist/zh-TW.mjs +0 -11
  222. package/dist/zh-TW.module.js +0 -11
  223. package/dist/zh-TW.module.js.map +0 -1
  224. package/src/MobileSearchAutocomplete.tsx +0 -632
  225. package/src/SearchAutocomplete.tsx +0 -382
  226. package/src/searchautocomplete.css +0 -71
@@ -1,632 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- import AlertMedium from '@spectrum-icons/ui/AlertMedium';
14
- import {AriaButtonProps} from '@react-types/button';
15
- import CheckmarkMedium from '@spectrum-icons/ui/CheckmarkMedium';
16
- import {classNames, useFocusableRef} from '@react-spectrum/utils';
17
- import {ClearButton} from '@react-spectrum/button';
18
- import {ComboBoxState, useComboBoxState} from '@react-stately/combobox';
19
- import {DismissButton, useOverlayTrigger} from '@react-aria/overlays';
20
- import {Field} from '@react-spectrum/label';
21
- import {FocusableRef, ValidationState} from '@react-types/shared';
22
- import {focusSafely, setInteractionModality, useHover} from '@react-aria/interactions';
23
- import {FocusScope, useFocusRing} from '@react-aria/focus';
24
- import {getActiveElement, mergeProps, useFormReset, useId} from '@react-aria/utils';
25
- // @ts-ignore
26
- import intlMessages from '../intl/*.json';
27
- import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
28
- import Magnifier from '@spectrum-icons/ui/Magnifier';
29
- import {ProgressCircle} from '@react-spectrum/progress';
30
- import React, {
31
- HTMLAttributes,
32
- InputHTMLAttributes,
33
- ReactElement,
34
- ReactNode,
35
- useCallback,
36
- useEffect,
37
- useRef,
38
- useState
39
- } from 'react';
40
- import searchAutocompleteStyles from './searchautocomplete.css';
41
- import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
42
- import {SpectrumSearchAutocompleteProps} from '@react-types/autocomplete';
43
- import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
44
- import {TextFieldBase} from '@react-spectrum/textfield';
45
- import textfieldStyles from '@adobe/spectrum-css-temp/components/textfield/vars.css';
46
- import {Tray} from '@react-spectrum/overlays';
47
- import {useButton} from '@react-aria/button';
48
- import {useDialog} from '@react-aria/dialog';
49
- import {useField} from '@react-aria/label';
50
- import {useFilter, useLocalizedStringFormatter} from '@react-aria/i18n';
51
- import {useFormValidation} from '@react-aria/form';
52
- import {useProviderProps} from '@react-spectrum/provider';
53
- import {useSearchAutocomplete} from '@react-aria/autocomplete';
54
-
55
- function ForwardMobileSearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
56
- props = useProviderProps(props);
57
-
58
- let {
59
- isQuiet,
60
- isDisabled,
61
- isRequired,
62
- validationBehavior,
63
- validate,
64
- name,
65
- isReadOnly,
66
- onSubmit = () => {}
67
- } = props;
68
-
69
- let {contains} = useFilter({sensitivity: 'base'});
70
- let state = useComboBoxState({
71
- ...props,
72
- defaultFilter: contains,
73
- allowsEmptyCollection: true,
74
- // Needs to be false here otherwise we double up on commitSelection/commitCustomValue calls when
75
- // user taps on underlay (i.e. initial tap will call setFocused(false) -> commitSelection/commitCustomValue via onBlur,
76
- // then the closing of the tray will call setFocused(false) again due to cleanup effect)
77
- shouldCloseOnBlur: false,
78
- allowsCustomValue: true,
79
- onSelectionChange: (key) => key !== null && onSubmit(null, key),
80
- selectedKey: undefined,
81
- defaultSelectedKey: undefined,
82
- validate: useCallback(v => validate?.(v.inputValue), [validate])
83
- });
84
-
85
- let buttonRef = useRef<HTMLDivElement>(null);
86
- let domRef = useFocusableRef(ref, buttonRef);
87
- let {triggerProps, overlayProps} = useOverlayTrigger({type: 'listbox'}, state, buttonRef);
88
-
89
- let inputRef = useRef<HTMLInputElement>(null);
90
- useFormValidation({
91
- ...props,
92
- focus: () => buttonRef.current?.focus()
93
- }, state, inputRef);
94
- let {isInvalid, validationErrors, validationDetails} = state.displayValidation;
95
- let validationState = props.validationState || (isInvalid ? 'invalid' : undefined);
96
- let errorMessage = props.errorMessage ?? validationErrors.join(' ');
97
-
98
- let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
99
- ...props,
100
- labelElementType: 'span',
101
- isInvalid,
102
- errorMessage
103
- });
104
-
105
- // Focus the button and show focus ring when clicking on the label
106
- labelProps.onClick = () => {
107
- if (!props.isDisabled && buttonRef.current) {
108
- buttonRef.current.focus();
109
- setInteractionModality('keyboard');
110
- }
111
- };
112
-
113
- let inputProps: InputHTMLAttributes<HTMLInputElement> = {
114
- type: 'hidden',
115
- name,
116
- value: state.inputValue
117
- };
118
-
119
- if (validationBehavior === 'native') {
120
- // Use a hidden <input type="text"> rather than <input type="hidden">
121
- // so that an empty value blocks HTML form submission when the field is required.
122
- inputProps.type = 'text';
123
- inputProps.hidden = true;
124
- inputProps.required = isRequired;
125
- // Ignore react warning.
126
- inputProps.onChange = () => {};
127
- }
128
-
129
- useFormReset(inputRef, state.defaultInputValue, state.setInputValue);
130
-
131
- return (
132
- <>
133
- <Field
134
- {...props}
135
- labelProps={labelProps}
136
- descriptionProps={descriptionProps}
137
- errorMessageProps={errorMessageProps}
138
- isInvalid={isInvalid}
139
- validationErrors={validationErrors}
140
- validationDetails={validationDetails}
141
- elementType="span"
142
- ref={domRef}
143
- includeNecessityIndicatorInAccessibilityName>
144
- <SearchAutocompleteButton
145
- {...mergeProps(triggerProps, fieldProps, {autoFocus: props.autoFocus, icon: props.icon})}
146
- ref={buttonRef}
147
- isQuiet={isQuiet}
148
- isDisabled={isDisabled}
149
- isReadOnly={isReadOnly}
150
- isPlaceholder={!state.inputValue}
151
- validationState={validationState}
152
- inputValue={state.inputValue}
153
- clearInput={() => state.setInputValue('')}
154
- onPress={() => !isReadOnly && state.open(null, 'manual')}>
155
- {state.inputValue || props.placeholder || ''}
156
- </SearchAutocompleteButton>
157
- </Field>
158
- <input {...inputProps} ref={inputRef} />
159
- <Tray state={state} isFixedHeight {...overlayProps}>
160
- <SearchAutocompleteTray
161
- {...props}
162
- onClose={state.close}
163
- overlayProps={overlayProps}
164
- state={state} />
165
- </Tray>
166
- </>
167
- );
168
- }
169
-
170
- export let MobileSearchAutocomplete = React.forwardRef(ForwardMobileSearchAutocomplete) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
171
-
172
-
173
- interface SearchAutocompleteButtonProps extends AriaButtonProps {
174
- icon?: ReactElement | null,
175
- isQuiet?: boolean,
176
- isDisabled?: boolean,
177
- isReadOnly?: boolean,
178
- isPlaceholder?: boolean,
179
- validationState?: ValidationState,
180
- inputValue?: string,
181
- clearInput?: () => void,
182
- children?: ReactNode,
183
- style?: React.CSSProperties,
184
- className?: string
185
- }
186
-
187
- // any type is because we don't want to call useObjectRef because this is an internal component and we know
188
- // we are always passing an object ref
189
- const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteButton(props: SearchAutocompleteButtonProps, ref: any) {
190
- let searchIcon = (
191
- <Magnifier data-testid="searchicon" />
192
- );
193
-
194
- let {
195
- icon = searchIcon,
196
- isQuiet,
197
- isDisabled,
198
- isReadOnly,
199
- isPlaceholder,
200
- validationState,
201
- inputValue,
202
- clearInput,
203
- children,
204
- style,
205
- className
206
- } = props;
207
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/autocomplete');
208
- let valueId = useId();
209
- let invalidId = useId();
210
- let validationIcon = validationState === 'invalid'
211
- ? <AlertMedium id={invalidId} aria-label={stringFormatter.format('invalid')} />
212
- : <CheckmarkMedium />;
213
-
214
- if (icon) {
215
- icon = React.cloneElement(icon, {
216
- UNSAFE_className: classNames(
217
- textfieldStyles,
218
- 'spectrum-Textfield-icon'
219
- ),
220
- size: 'S'
221
- });
222
- }
223
-
224
- let clearButton = (
225
- <ClearButton
226
- onPress={(e) => {
227
- clearInput?.();
228
- props?.onPress?.(e);
229
- }}
230
- preventFocus
231
- aria-label={stringFormatter.format('clear')}
232
- excludeFromTabOrder
233
- UNSAFE_className={
234
- classNames(
235
- searchStyles,
236
- 'spectrum-ClearButton'
237
- )
238
- }
239
- isDisabled={isDisabled} />
240
- );
241
-
242
- let validation = React.cloneElement(validationIcon, {
243
- UNSAFE_className: classNames(
244
- textfieldStyles,
245
- 'spectrum-Textfield-validationIcon',
246
- classNames(
247
- styles,
248
- 'spectrum-InputGroup-input-validationIcon'
249
- ),
250
- classNames(
251
- searchStyles,
252
- 'spectrum-Search-validationIcon'
253
- )
254
- )
255
- });
256
-
257
- let {hoverProps, isHovered} = useHover({});
258
- let {isFocused, isFocusVisible, focusProps} = useFocusRing();
259
- let {buttonProps} = useButton({
260
- ...props,
261
- 'aria-labelledby': [
262
- props['aria-labelledby'],
263
- props['aria-label'] && !props['aria-labelledby'] ? props.id : null,
264
- valueId,
265
- validationState === 'invalid' ? invalidId : null
266
- ].filter(Boolean).join(' '),
267
- elementType: 'div'
268
- }, ref);
269
-
270
- return (
271
- <div
272
- {...mergeProps(hoverProps, focusProps, buttonProps)}
273
- aria-haspopup="dialog"
274
- ref={ref}
275
- style={{...style, outline: 'none'}}
276
- className={
277
- classNames(
278
- styles,
279
- 'spectrum-InputGroup',
280
- {
281
- 'spectrum-InputGroup--quiet': isQuiet,
282
- 'is-disabled': isDisabled,
283
- 'spectrum-InputGroup--invalid': validationState === 'invalid' && !isDisabled,
284
- 'is-hovered': isHovered,
285
- 'is-focused': isFocused,
286
- 'focus-ring': isFocusVisible
287
- },
288
- classNames(
289
- searchAutocompleteStyles,
290
- 'searchautocomplete',
291
- 'mobile-searchautocomplete'
292
- ),
293
- className
294
- )
295
- }>
296
- <div
297
- className={
298
- classNames(
299
- textfieldStyles,
300
- 'spectrum-Textfield',
301
- {
302
- 'spectrum-Textfield--invalid': validationState === 'invalid' && !isDisabled,
303
- 'spectrum-Textfield--valid': validationState === 'valid' && !isDisabled,
304
- 'spectrum-Textfield--quiet': isQuiet
305
- },
306
- classNames(
307
- searchStyles,
308
- 'spectrum-Search',
309
- 'spectrum-Search--loadable',
310
- {
311
- 'is-disabled': isDisabled,
312
- 'is-quiet': isQuiet,
313
- 'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
314
- 'spectrum-Search--valid': validationState === 'valid' && !isDisabled
315
- }
316
- ),
317
- classNames(
318
- styles,
319
- 'spectrum-InputGroup-field'
320
- )
321
- )
322
- }>
323
- <div
324
- className={
325
- classNames(
326
- textfieldStyles,
327
- 'spectrum-Textfield-input',
328
- {
329
- 'spectrum-Textfield-inputIcon': !!icon,
330
- 'is-hovered': isHovered,
331
- 'is-placeholder': isPlaceholder,
332
- 'is-disabled': isDisabled,
333
- 'is-quiet': isQuiet,
334
- 'is-focused': isFocused
335
- },
336
- classNames(
337
- searchStyles,
338
- 'spectrum-Search-input'
339
- ),
340
- classNames(
341
- searchAutocompleteStyles,
342
- 'mobile-input'
343
- )
344
- )
345
- }>
346
- {icon}
347
- <span
348
- id={valueId}
349
- className={
350
- classNames(
351
- searchAutocompleteStyles,
352
- 'mobile-value'
353
- )
354
- }>
355
- {children}
356
- </span>
357
- </div>
358
- {validationState && !isDisabled ? validation : null}
359
- {(inputValue !== '' || validationState != null) && !isReadOnly && clearButton}
360
- </div>
361
- </div>
362
- );
363
- });
364
-
365
- interface SearchAutocompleteTrayProps<T> extends SpectrumSearchAutocompleteProps<T> {
366
- state: ComboBoxState<T>,
367
- overlayProps: HTMLAttributes<HTMLElement>,
368
- loadingIndicator?: ReactElement,
369
- onClose: () => void
370
- }
371
-
372
- function SearchAutocompleteTray<T>(props: SearchAutocompleteTrayProps<T>) {
373
- let searchIcon = (
374
- <Magnifier data-testid="searchicon" />
375
- );
376
-
377
- let {
378
- // completionMode = 'suggest',
379
- state,
380
- icon = searchIcon,
381
- isDisabled,
382
- validationState,
383
- label,
384
- overlayProps,
385
- loadingState,
386
- onLoadMore,
387
- onClose,
388
- onSubmit
389
- } = props;
390
-
391
- let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
392
- let [showLoading, setShowLoading] = useState(false);
393
- let inputRef = useRef<HTMLInputElement>(null);
394
- let popoverRef = useRef<HTMLDivElement>(null);
395
- let listBoxRef = useRef<HTMLDivElement>(null);
396
- let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
397
- let layout = useListBoxLayout();
398
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/autocomplete');
399
-
400
- let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete<T>(
401
- {
402
- ...props,
403
- layoutDelegate: layout,
404
- popoverRef: popoverRef,
405
- listBoxRef,
406
- inputRef,
407
- // Handled outside the tray.
408
- name: undefined
409
- },
410
- state
411
- );
412
-
413
- React.useEffect(() => {
414
- if (inputRef.current) {
415
- focusSafely(inputRef.current);
416
- }
417
- }, []);
418
-
419
- React.useEffect(() => {
420
- // When the tray closes, set state.isFocused (i.e. the tray input's focus tracker) to false.
421
- // This is to prevent state.isFocused from being set to true when the tray closes via tapping on the underlay
422
- // (FocusScope attempts to restore focus to the tray input when tapping outside the tray due to "contain")
423
- // Have to do this manually since React doesn't call onBlur when a component is unmounted: https://github.com/facebook/react/issues/12363
424
- if (!state.isOpen && state.isFocused) {
425
- state.setFocused(false);
426
- }
427
- });
428
-
429
- let {dialogProps} = useDialog({
430
- 'aria-labelledby': useId(labelProps.id)
431
- }, popoverRef);
432
-
433
- // Override the role of the input to "searchbox" instead of "combobox".
434
- // Since the listbox is always visible, the combobox role doesn't really give us anything.
435
- // VoiceOver on iOS reads "double tap to collapse" when focused on the input rather than
436
- // "double tap to edit text", as with a textbox or searchbox. We'd like double tapping to
437
- // open the virtual keyboard rather than closing the tray.
438
- inputProps.role = 'searchbox';
439
- inputProps['aria-haspopup'] = 'listbox';
440
- delete inputProps.onTouchEnd;
441
-
442
- let clearButton = (
443
- <ClearButton
444
- {...clearButtonProps}
445
- preventFocus
446
- aria-label={stringFormatter.format('clear')}
447
- excludeFromTabOrder
448
- UNSAFE_className={
449
- classNames(
450
- searchStyles,
451
- 'spectrum-ClearButton'
452
- )
453
- }
454
- isDisabled={isDisabled} />
455
- );
456
-
457
- let loadingCircle = (
458
- <ProgressCircle
459
- aria-label={stringFormatter.format('loading')}
460
- size="S"
461
- isIndeterminate
462
- UNSAFE_className={classNames(
463
- searchStyles,
464
- 'spectrum-Search-circleLoader',
465
- classNames(
466
- textfieldStyles,
467
- 'spectrum-Textfield-circleLoader'
468
- )
469
- )} />
470
- );
471
-
472
- // Close the software keyboard on scroll to give the user a bigger area to scroll.
473
- // But only do this if scrolling with touch, otherwise it can cause issues with touch
474
- // screen readers.
475
- let isTouchDown = useRef(false);
476
- let onTouchStart = () => {
477
- isTouchDown.current = true;
478
- };
479
-
480
- let onTouchEnd = () => {
481
- isTouchDown.current = false;
482
- };
483
-
484
- let onScroll = useCallback(() => {
485
- if (!inputRef.current || getActiveElement() !== inputRef.current || !isTouchDown.current) {
486
- return;
487
- }
488
-
489
- if (popoverRef.current) {
490
- popoverRef.current.focus();
491
- }
492
- }, [inputRef, popoverRef, isTouchDown]);
493
-
494
- let inputValue = inputProps.value;
495
- let lastInputValue = useRef(inputValue);
496
- useEffect(() => {
497
- if (loadingState === 'filtering' && !showLoading) {
498
- if (timeout.current === null) {
499
- timeout.current = setTimeout(() => {
500
- setShowLoading(true);
501
- }, 500);
502
- }
503
-
504
- // If user is typing, clear the timer and restart since it is a new request
505
- if (inputValue !== lastInputValue.current) {
506
- clearTimeout(timeout.current);
507
- timeout.current = setTimeout(() => {
508
- setShowLoading(true);
509
- }, 500);
510
- }
511
- } else if (loadingState !== 'filtering') {
512
- // If loading is no longer happening, clear any timers and hide the loading circle
513
- setShowLoading(false);
514
- if (timeout.current !== null) {
515
- clearTimeout(timeout.current);
516
- timeout.current = null;
517
- }
518
- }
519
-
520
- lastInputValue.current = inputValue;
521
- }, [loadingState, inputValue, showLoading]);
522
-
523
- let onKeyDown = (e) => {
524
- // Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
525
- if (e.key === 'Enter' && state.selectionManager.focusedKey == null) {
526
- popoverRef.current?.focus();
527
- if (onClose) {
528
- onClose();
529
- }
530
- if (onSubmit) {
531
- onSubmit(inputValue == null ? null : inputValue.toString(), null);
532
- }
533
- } else {
534
- if (inputProps.onKeyDown) {
535
- inputProps.onKeyDown(e);
536
- }
537
- }
538
- };
539
-
540
- if (icon) {
541
- icon = React.cloneElement(icon, {
542
- UNSAFE_className: classNames(
543
- textfieldStyles,
544
- 'spectrum-Textfield-icon'
545
- ),
546
- size: 'S'
547
- });
548
- }
549
-
550
- return (
551
- <FocusScope restoreFocus contain>
552
- <div
553
- {...mergeProps(overlayProps, dialogProps)}
554
- ref={popoverRef}
555
- className={
556
- classNames(
557
- searchAutocompleteStyles,
558
- 'tray-dialog'
559
- )
560
- }>
561
- <DismissButton onDismiss={onClose} />
562
- <TextFieldBase
563
- label={label}
564
- labelProps={labelProps}
565
- inputProps={{...inputProps, onKeyDown}}
566
- inputRef={inputRef}
567
- isDisabled={isDisabled}
568
- isLoading={showLoading && loadingState === 'filtering'}
569
- loadingIndicator={loadingState != null ? loadingCircle : undefined}
570
- validationState={validationState}
571
- wrapperChildren={((state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly) ? clearButton : undefined}
572
- icon={icon}
573
- UNSAFE_className={
574
- classNames(
575
- searchStyles,
576
- 'spectrum-Search',
577
- 'spectrum-Textfield',
578
- 'spectrum-Search--loadable',
579
- {
580
- 'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
581
- 'spectrum-Search--valid': validationState === 'valid' && !isDisabled
582
- },
583
- classNames(
584
- searchAutocompleteStyles,
585
- 'tray-textfield',
586
- {
587
- 'has-label': !!props.label
588
- }
589
- )
590
- )
591
- }
592
- inputClassName={
593
- classNames(
594
- searchStyles,
595
- 'spectrum-Search-input'
596
- )
597
- }
598
- validationIconClassName={
599
- classNames(
600
- searchStyles,
601
- 'spectrum-Search-validationIcon'
602
- )
603
- } />
604
- <ListBoxBase
605
- {...listBoxProps}
606
- domProps={{onTouchStart, onTouchEnd}}
607
- disallowEmptySelection
608
- shouldSelectOnPressUp
609
- focusOnPointerEnter
610
- layout={layout}
611
- state={state}
612
- shouldUseVirtualFocus
613
- renderEmptyState={() => loadingState !== 'loading' && (
614
- <span className={classNames(searchAutocompleteStyles, 'no-results')}>
615
- {stringFormatter.format('noResults')}
616
- </span>
617
- )}
618
- UNSAFE_className={
619
- classNames(
620
- searchAutocompleteStyles,
621
- 'tray-listbox'
622
- )
623
- }
624
- ref={listBoxRef}
625
- onScroll={onScroll}
626
- onLoadMore={onLoadMore}
627
- isLoading={isLoading} />
628
- <DismissButton onDismiss={onClose} />
629
- </div>
630
- </FocusScope>
631
- );
632
- }