@react-spectrum/autocomplete 3.0.0-alpha.5 → 3.0.0-alpha.51
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/MobileSearchAutocomplete.main.js +449 -0
- package/dist/MobileSearchAutocomplete.main.js.map +1 -0
- package/dist/MobileSearchAutocomplete.mjs +444 -0
- package/dist/MobileSearchAutocomplete.module.js +444 -0
- package/dist/MobileSearchAutocomplete.module.js.map +1 -0
- package/dist/SearchAutocomplete.main.js +305 -0
- package/dist/SearchAutocomplete.main.js.map +1 -0
- package/dist/SearchAutocomplete.mjs +300 -0
- package/dist/SearchAutocomplete.module.js +300 -0
- package/dist/SearchAutocomplete.module.js.map +1 -0
- package/dist/ar-AE.main.js +9 -0
- package/dist/ar-AE.main.js.map +1 -0
- package/dist/ar-AE.mjs +11 -0
- package/dist/ar-AE.module.js +11 -0
- package/dist/ar-AE.module.js.map +1 -0
- package/dist/autocomplete.02db68b2.css +55 -0
- package/dist/autocomplete.02db68b2.css.map +1 -0
- package/dist/autocomplete.102c5c14.css +223 -0
- package/dist/autocomplete.102c5c14.css.map +1 -0
- package/dist/autocomplete.30e64bfb.css +550 -0
- package/dist/autocomplete.30e64bfb.css.map +1 -0
- package/dist/autocomplete.c4adfff6.css +649 -0
- package/dist/autocomplete.c4adfff6.css.map +1 -0
- package/dist/bg-BG.main.js +9 -0
- package/dist/bg-BG.main.js.map +1 -0
- package/dist/bg-BG.mjs +11 -0
- package/dist/bg-BG.module.js +11 -0
- package/dist/bg-BG.module.js.map +1 -0
- package/dist/cs-CZ.main.js +9 -0
- package/dist/cs-CZ.main.js.map +1 -0
- package/dist/cs-CZ.mjs +11 -0
- package/dist/cs-CZ.module.js +11 -0
- package/dist/cs-CZ.module.js.map +1 -0
- package/dist/da-DK.main.js +9 -0
- package/dist/da-DK.main.js.map +1 -0
- package/dist/da-DK.mjs +11 -0
- package/dist/da-DK.module.js +11 -0
- package/dist/da-DK.module.js.map +1 -0
- package/dist/de-DE.main.js +9 -0
- package/dist/de-DE.main.js.map +1 -0
- package/dist/de-DE.mjs +11 -0
- package/dist/de-DE.module.js +11 -0
- package/dist/de-DE.module.js.map +1 -0
- package/dist/el-GR.main.js +9 -0
- package/dist/el-GR.main.js.map +1 -0
- package/dist/el-GR.mjs +11 -0
- package/dist/el-GR.module.js +11 -0
- package/dist/el-GR.module.js.map +1 -0
- package/dist/en-US.main.js +9 -0
- package/dist/en-US.main.js.map +1 -0
- package/dist/en-US.mjs +11 -0
- package/dist/en-US.module.js +11 -0
- package/dist/en-US.module.js.map +1 -0
- package/dist/es-ES.main.js +9 -0
- package/dist/es-ES.main.js.map +1 -0
- package/dist/es-ES.mjs +11 -0
- package/dist/es-ES.module.js +11 -0
- package/dist/es-ES.module.js.map +1 -0
- package/dist/et-EE.main.js +9 -0
- package/dist/et-EE.main.js.map +1 -0
- package/dist/et-EE.mjs +11 -0
- package/dist/et-EE.module.js +11 -0
- package/dist/et-EE.module.js.map +1 -0
- package/dist/fi-FI.main.js +9 -0
- package/dist/fi-FI.main.js.map +1 -0
- package/dist/fi-FI.mjs +11 -0
- package/dist/fi-FI.module.js +11 -0
- package/dist/fi-FI.module.js.map +1 -0
- package/dist/fr-FR.main.js +9 -0
- package/dist/fr-FR.main.js.map +1 -0
- package/dist/fr-FR.mjs +11 -0
- package/dist/fr-FR.module.js +11 -0
- package/dist/fr-FR.module.js.map +1 -0
- package/dist/he-IL.main.js +9 -0
- package/dist/he-IL.main.js.map +1 -0
- package/dist/he-IL.mjs +11 -0
- package/dist/he-IL.module.js +11 -0
- package/dist/he-IL.module.js.map +1 -0
- package/dist/hr-HR.main.js +9 -0
- package/dist/hr-HR.main.js.map +1 -0
- package/dist/hr-HR.mjs +11 -0
- package/dist/hr-HR.module.js +11 -0
- package/dist/hr-HR.module.js.map +1 -0
- package/dist/hu-HU.main.js +9 -0
- package/dist/hu-HU.main.js.map +1 -0
- package/dist/hu-HU.mjs +11 -0
- package/dist/hu-HU.module.js +11 -0
- package/dist/hu-HU.module.js.map +1 -0
- package/dist/import.mjs +20 -0
- package/dist/inputgroup_vars_css.main.js +86 -0
- package/dist/inputgroup_vars_css.main.js.map +1 -0
- package/dist/inputgroup_vars_css.mjs +88 -0
- package/dist/inputgroup_vars_css.module.js +88 -0
- package/dist/inputgroup_vars_css.module.js.map +1 -0
- package/dist/intlStrings.main.js +108 -0
- package/dist/intlStrings.main.js.map +1 -0
- package/dist/intlStrings.mjs +110 -0
- package/dist/intlStrings.module.js +110 -0
- package/dist/intlStrings.module.js.map +1 -0
- package/dist/it-IT.main.js +9 -0
- package/dist/it-IT.main.js.map +1 -0
- package/dist/it-IT.mjs +11 -0
- package/dist/it-IT.module.js +11 -0
- package/dist/it-IT.module.js.map +1 -0
- package/dist/ja-JP.main.js +9 -0
- package/dist/ja-JP.main.js.map +1 -0
- package/dist/ja-JP.mjs +11 -0
- package/dist/ja-JP.module.js +11 -0
- package/dist/ja-JP.module.js.map +1 -0
- package/dist/ko-KR.main.js +9 -0
- package/dist/ko-KR.main.js.map +1 -0
- package/dist/ko-KR.mjs +11 -0
- package/dist/ko-KR.module.js +11 -0
- package/dist/ko-KR.module.js.map +1 -0
- package/dist/lt-LT.main.js +9 -0
- package/dist/lt-LT.main.js.map +1 -0
- package/dist/lt-LT.mjs +11 -0
- package/dist/lt-LT.module.js +11 -0
- package/dist/lt-LT.module.js.map +1 -0
- package/dist/lv-LV.main.js +9 -0
- package/dist/lv-LV.main.js.map +1 -0
- package/dist/lv-LV.mjs +11 -0
- package/dist/lv-LV.module.js +11 -0
- package/dist/lv-LV.module.js.map +1 -0
- package/dist/main.js +14 -997
- package/dist/main.js.map +1 -1
- package/dist/module.js +13 -984
- package/dist/module.js.map +1 -1
- package/dist/nb-NO.main.js +9 -0
- package/dist/nb-NO.main.js.map +1 -0
- package/dist/nb-NO.mjs +11 -0
- package/dist/nb-NO.module.js +11 -0
- package/dist/nb-NO.module.js.map +1 -0
- package/dist/nl-NL.main.js +9 -0
- package/dist/nl-NL.main.js.map +1 -0
- package/dist/nl-NL.mjs +11 -0
- package/dist/nl-NL.module.js +11 -0
- package/dist/nl-NL.module.js.map +1 -0
- package/dist/pl-PL.main.js +9 -0
- package/dist/pl-PL.main.js.map +1 -0
- package/dist/pl-PL.mjs +11 -0
- package/dist/pl-PL.module.js +11 -0
- package/dist/pl-PL.module.js.map +1 -0
- package/dist/pt-BR.main.js +9 -0
- package/dist/pt-BR.main.js.map +1 -0
- package/dist/pt-BR.mjs +11 -0
- package/dist/pt-BR.module.js +11 -0
- package/dist/pt-BR.module.js.map +1 -0
- package/dist/pt-PT.main.js +9 -0
- package/dist/pt-PT.main.js.map +1 -0
- package/dist/pt-PT.mjs +11 -0
- package/dist/pt-PT.module.js +11 -0
- package/dist/pt-PT.module.js.map +1 -0
- package/dist/ro-RO.main.js +9 -0
- package/dist/ro-RO.main.js.map +1 -0
- package/dist/ro-RO.mjs +11 -0
- package/dist/ro-RO.module.js +11 -0
- package/dist/ro-RO.module.js.map +1 -0
- package/dist/ru-RU.main.js +9 -0
- package/dist/ru-RU.main.js.map +1 -0
- package/dist/ru-RU.mjs +11 -0
- package/dist/ru-RU.module.js +11 -0
- package/dist/ru-RU.module.js.map +1 -0
- package/dist/search_vars_css.main.js +53 -0
- package/dist/search_vars_css.main.js.map +1 -0
- package/dist/search_vars_css.mjs +55 -0
- package/dist/search_vars_css.module.js +55 -0
- package/dist/search_vars_css.module.js.map +1 -0
- package/dist/searchautocomplete_css.main.js +38 -0
- package/dist/searchautocomplete_css.main.js.map +1 -0
- package/dist/searchautocomplete_css.mjs +40 -0
- package/dist/searchautocomplete_css.module.js +40 -0
- package/dist/searchautocomplete_css.module.js.map +1 -0
- package/dist/sk-SK.main.js +9 -0
- package/dist/sk-SK.main.js.map +1 -0
- package/dist/sk-SK.mjs +11 -0
- package/dist/sk-SK.module.js +11 -0
- package/dist/sk-SK.module.js.map +1 -0
- package/dist/sl-SI.main.js +9 -0
- package/dist/sl-SI.main.js.map +1 -0
- package/dist/sl-SI.mjs +11 -0
- package/dist/sl-SI.module.js +11 -0
- package/dist/sl-SI.module.js.map +1 -0
- package/dist/sr-SP.main.js +9 -0
- package/dist/sr-SP.main.js.map +1 -0
- package/dist/sr-SP.mjs +11 -0
- package/dist/sr-SP.module.js +11 -0
- package/dist/sr-SP.module.js.map +1 -0
- package/dist/sv-SE.main.js +9 -0
- package/dist/sv-SE.main.js.map +1 -0
- package/dist/sv-SE.mjs +11 -0
- package/dist/sv-SE.module.js +11 -0
- package/dist/sv-SE.module.js.map +1 -0
- package/dist/textfield_vars_css.main.js +74 -0
- package/dist/textfield_vars_css.main.js.map +1 -0
- package/dist/textfield_vars_css.mjs +76 -0
- package/dist/textfield_vars_css.module.js +76 -0
- package/dist/textfield_vars_css.module.js.map +1 -0
- package/dist/tr-TR.main.js +9 -0
- package/dist/tr-TR.main.js.map +1 -0
- package/dist/tr-TR.mjs +11 -0
- package/dist/tr-TR.module.js +11 -0
- package/dist/tr-TR.module.js.map +1 -0
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/uk-UA.main.js +9 -0
- package/dist/uk-UA.main.js.map +1 -0
- package/dist/uk-UA.mjs +11 -0
- package/dist/uk-UA.module.js +11 -0
- package/dist/uk-UA.module.js.map +1 -0
- package/dist/zh-CN.main.js +9 -0
- package/dist/zh-CN.main.js.map +1 -0
- package/dist/zh-CN.mjs +11 -0
- package/dist/zh-CN.module.js +11 -0
- package/dist/zh-CN.module.js.map +1 -0
- package/dist/zh-TW.main.js +9 -0
- package/dist/zh-TW.main.js.map +1 -0
- package/dist/zh-TW.mjs +11 -0
- package/dist/zh-TW.module.js +11 -0
- package/dist/zh-TW.module.js.map +1 -0
- package/package.json +38 -29
- package/src/MobileSearchAutocomplete.tsx +228 -143
- package/src/SearchAutocomplete.tsx +125 -78
- package/src/index.ts +2 -3
- package/src/searchautocomplete.css +11 -5
- package/dist/main.css +0 -1
|
@@ -13,24 +13,32 @@
|
|
|
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 {
|
|
22
|
+
import {focusSafely, setInteractionModality, useHover} from '@react-aria/interactions';
|
|
23
|
+
import {FocusScope, useFocusRing} from '@react-aria/focus';
|
|
24
24
|
// @ts-ignore
|
|
25
25
|
import intlMessages from '../intl/*.json';
|
|
26
26
|
import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
|
|
27
27
|
import Magnifier from '@spectrum-icons/ui/Magnifier';
|
|
28
|
-
import {mergeProps, useId} from '@react-aria/utils';
|
|
28
|
+
import {mergeProps, useFormReset, useId} from '@react-aria/utils';
|
|
29
29
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
30
|
-
import React, {
|
|
30
|
+
import React, {
|
|
31
|
+
HTMLAttributes,
|
|
32
|
+
InputHTMLAttributes,
|
|
33
|
+
ReactElement,
|
|
34
|
+
ReactNode,
|
|
35
|
+
useCallback,
|
|
36
|
+
useEffect,
|
|
37
|
+
useRef,
|
|
38
|
+
useState
|
|
39
|
+
} from 'react';
|
|
31
40
|
import searchAutocompleteStyles from './searchautocomplete.css';
|
|
32
41
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
33
|
-
import {setInteractionModality, useHover} from '@react-aria/interactions';
|
|
34
42
|
import {SpectrumSearchAutocompleteProps} from '@react-types/autocomplete';
|
|
35
43
|
import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
|
|
36
44
|
import {TextFieldBase} from '@react-spectrum/textfield';
|
|
@@ -38,20 +46,22 @@ import textfieldStyles from '@adobe/spectrum-css-temp/components/textfield/vars.
|
|
|
38
46
|
import {Tray} from '@react-spectrum/overlays';
|
|
39
47
|
import {useButton} from '@react-aria/button';
|
|
40
48
|
import {useDialog} from '@react-aria/dialog';
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {useOverlayTrigger} from '@react-aria/overlays';
|
|
49
|
+
import {useField} from '@react-aria/label';
|
|
50
|
+
import {useFilter, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
51
|
+
import {useFormValidation} from '@react-aria/form';
|
|
45
52
|
import {useProviderProps} from '@react-spectrum/provider';
|
|
46
53
|
import {useSearchAutocomplete} from '@react-aria/autocomplete';
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
function ForwardMobileSearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
49
56
|
props = useProviderProps(props);
|
|
50
57
|
|
|
51
58
|
let {
|
|
52
59
|
isQuiet,
|
|
53
60
|
isDisabled,
|
|
54
|
-
|
|
61
|
+
isRequired,
|
|
62
|
+
validationBehavior,
|
|
63
|
+
validate,
|
|
64
|
+
name,
|
|
55
65
|
isReadOnly,
|
|
56
66
|
onSubmit = () => {}
|
|
57
67
|
} = props;
|
|
@@ -68,38 +78,71 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
68
78
|
allowsCustomValue: true,
|
|
69
79
|
onSelectionChange: (key) => key !== null && onSubmit(null, key),
|
|
70
80
|
selectedKey: undefined,
|
|
71
|
-
defaultSelectedKey: undefined
|
|
81
|
+
defaultSelectedKey: undefined,
|
|
82
|
+
validate: useCallback(v => validate?.(v.inputValue), [validate])
|
|
72
83
|
});
|
|
73
|
-
|
|
74
|
-
let buttonRef = useRef<
|
|
84
|
+
|
|
85
|
+
let buttonRef = useRef<HTMLDivElement>(null);
|
|
75
86
|
let domRef = useFocusableRef(ref, buttonRef);
|
|
76
87
|
let {triggerProps, overlayProps} = useOverlayTrigger({type: 'listbox'}, state, buttonRef);
|
|
77
88
|
|
|
78
|
-
let
|
|
89
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
90
|
+
useFormValidation({
|
|
79
91
|
...props,
|
|
80
|
-
|
|
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
|
|
81
103
|
});
|
|
82
104
|
|
|
83
105
|
// Focus the button and show focus ring when clicking on the label
|
|
84
106
|
labelProps.onClick = () => {
|
|
85
|
-
if (!props.isDisabled) {
|
|
107
|
+
if (!props.isDisabled && buttonRef.current) {
|
|
86
108
|
buttonRef.current.focus();
|
|
87
109
|
setInteractionModality('keyboard');
|
|
88
110
|
}
|
|
89
111
|
};
|
|
90
112
|
|
|
91
|
-
let
|
|
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);
|
|
92
130
|
|
|
93
131
|
return (
|
|
94
132
|
<>
|
|
95
133
|
<Field
|
|
96
134
|
{...props}
|
|
97
135
|
labelProps={labelProps}
|
|
136
|
+
descriptionProps={descriptionProps}
|
|
137
|
+
errorMessageProps={errorMessageProps}
|
|
138
|
+
isInvalid={isInvalid}
|
|
139
|
+
validationErrors={validationErrors}
|
|
140
|
+
validationDetails={validationDetails}
|
|
98
141
|
elementType="span"
|
|
99
142
|
ref={domRef}
|
|
100
143
|
includeNecessityIndicatorInAccessibilityName>
|
|
101
144
|
<SearchAutocompleteButton
|
|
102
|
-
{...mergeProps(triggerProps, fieldProps, {autoFocus: props.autoFocus})}
|
|
145
|
+
{...mergeProps(triggerProps, fieldProps, {autoFocus: props.autoFocus, icon: props.icon})}
|
|
103
146
|
ref={buttonRef}
|
|
104
147
|
isQuiet={isQuiet}
|
|
105
148
|
isDisabled={isDisabled}
|
|
@@ -112,18 +155,23 @@ export const MobileSearchAutocomplete = React.forwardRef(function MobileSearchAu
|
|
|
112
155
|
{state.inputValue || props.placeholder || ''}
|
|
113
156
|
</SearchAutocompleteButton>
|
|
114
157
|
</Field>
|
|
115
|
-
<
|
|
158
|
+
<input {...inputProps} ref={inputRef} />
|
|
159
|
+
<Tray state={state} isFixedHeight {...overlayProps}>
|
|
116
160
|
<SearchAutocompleteTray
|
|
117
161
|
{...props}
|
|
118
|
-
onClose={
|
|
162
|
+
onClose={state.close}
|
|
119
163
|
overlayProps={overlayProps}
|
|
120
164
|
state={state} />
|
|
121
165
|
</Tray>
|
|
122
166
|
</>
|
|
123
167
|
);
|
|
124
|
-
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export let MobileSearchAutocomplete = React.forwardRef(ForwardMobileSearchAutocomplete) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
171
|
+
|
|
125
172
|
|
|
126
173
|
interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
174
|
+
icon?: ReactElement | null,
|
|
127
175
|
isQuiet?: boolean,
|
|
128
176
|
isDisabled?: boolean,
|
|
129
177
|
isReadOnly?: boolean,
|
|
@@ -136,8 +184,15 @@ interface SearchAutocompleteButtonProps extends AriaButtonProps {
|
|
|
136
184
|
className?: string
|
|
137
185
|
}
|
|
138
186
|
|
|
139
|
-
|
|
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
|
+
|
|
140
194
|
let {
|
|
195
|
+
icon = searchIcon,
|
|
141
196
|
isQuiet,
|
|
142
197
|
isDisabled,
|
|
143
198
|
isReadOnly,
|
|
@@ -149,33 +204,31 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
149
204
|
style,
|
|
150
205
|
className
|
|
151
206
|
} = props;
|
|
152
|
-
let
|
|
207
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/autocomplete');
|
|
153
208
|
let valueId = useId();
|
|
154
209
|
let invalidId = useId();
|
|
155
210
|
let validationIcon = validationState === 'invalid'
|
|
156
|
-
? <AlertMedium id={invalidId} aria-label={
|
|
211
|
+
? <AlertMedium id={invalidId} aria-label={stringFormatter.format('invalid')} />
|
|
157
212
|
: <CheckmarkMedium />;
|
|
158
213
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
size: 'S'
|
|
169
|
-
});
|
|
214
|
+
if (icon) {
|
|
215
|
+
icon = React.cloneElement(icon, {
|
|
216
|
+
UNSAFE_className: classNames(
|
|
217
|
+
textfieldStyles,
|
|
218
|
+
'spectrum-Textfield-icon'
|
|
219
|
+
),
|
|
220
|
+
size: 'S'
|
|
221
|
+
});
|
|
222
|
+
}
|
|
170
223
|
|
|
171
224
|
let clearButton = (
|
|
172
225
|
<ClearButton
|
|
173
226
|
onPress={(e) => {
|
|
174
|
-
clearInput();
|
|
175
|
-
props
|
|
227
|
+
clearInput?.();
|
|
228
|
+
props?.onPress?.(e);
|
|
176
229
|
}}
|
|
177
230
|
preventFocus
|
|
178
|
-
aria-label={
|
|
231
|
+
aria-label={stringFormatter.format('clear')}
|
|
179
232
|
excludeFromTabOrder
|
|
180
233
|
UNSAFE_className={
|
|
181
234
|
classNames(
|
|
@@ -186,7 +239,6 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
186
239
|
isDisabled={isDisabled} />
|
|
187
240
|
);
|
|
188
241
|
|
|
189
|
-
|
|
190
242
|
let validation = React.cloneElement(validationIcon, {
|
|
191
243
|
UNSAFE_className: classNames(
|
|
192
244
|
textfieldStyles,
|
|
@@ -194,11 +246,16 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
194
246
|
classNames(
|
|
195
247
|
styles,
|
|
196
248
|
'spectrum-InputGroup-input-validationIcon'
|
|
249
|
+
),
|
|
250
|
+
classNames(
|
|
251
|
+
searchStyles,
|
|
252
|
+
'spectrum-Search-validationIcon'
|
|
197
253
|
)
|
|
198
254
|
)
|
|
199
255
|
});
|
|
200
256
|
|
|
201
257
|
let {hoverProps, isHovered} = useHover({});
|
|
258
|
+
let {isFocused, isFocusVisible, focusProps} = useFocusRing();
|
|
202
259
|
let {buttonProps} = useButton({
|
|
203
260
|
...props,
|
|
204
261
|
'aria-labelledby': [
|
|
@@ -211,102 +268,116 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut
|
|
|
211
268
|
}, ref);
|
|
212
269
|
|
|
213
270
|
return (
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
}>
|
|
217
296
|
<div
|
|
218
|
-
{...mergeProps(hoverProps, buttonProps)}
|
|
219
|
-
aria-haspopup="dialog"
|
|
220
|
-
ref={ref as RefObject<HTMLDivElement>}
|
|
221
|
-
style={{...style, outline: 'none'}}
|
|
222
297
|
className={
|
|
223
298
|
classNames(
|
|
224
|
-
|
|
225
|
-
'spectrum-
|
|
299
|
+
textfieldStyles,
|
|
300
|
+
'spectrum-Textfield',
|
|
226
301
|
{
|
|
227
|
-
'spectrum-
|
|
228
|
-
'
|
|
229
|
-
'spectrum-
|
|
230
|
-
'is-hovered': isHovered
|
|
302
|
+
'spectrum-Textfield--invalid': validationState === 'invalid' && !isDisabled,
|
|
303
|
+
'spectrum-Textfield--valid': validationState === 'valid' && !isDisabled,
|
|
304
|
+
'spectrum-Textfield--quiet': isQuiet
|
|
231
305
|
},
|
|
232
306
|
classNames(
|
|
233
|
-
|
|
234
|
-
'
|
|
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
|
+
}
|
|
235
316
|
),
|
|
236
|
-
|
|
317
|
+
classNames(
|
|
318
|
+
styles,
|
|
319
|
+
'spectrum-InputGroup-field'
|
|
320
|
+
)
|
|
237
321
|
)
|
|
238
322
|
}>
|
|
239
323
|
<div
|
|
240
324
|
className={
|
|
241
325
|
classNames(
|
|
242
326
|
textfieldStyles,
|
|
243
|
-
'spectrum-Textfield',
|
|
327
|
+
'spectrum-Textfield-input',
|
|
244
328
|
{
|
|
245
|
-
'spectrum-Textfield
|
|
246
|
-
'
|
|
247
|
-
'
|
|
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
|
|
248
335
|
},
|
|
249
336
|
classNames(
|
|
250
337
|
searchStyles,
|
|
251
|
-
'spectrum-Search'
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
257
|
-
}
|
|
338
|
+
'spectrum-Search-input'
|
|
339
|
+
),
|
|
340
|
+
classNames(
|
|
341
|
+
searchAutocompleteStyles,
|
|
342
|
+
'mobile-input'
|
|
258
343
|
)
|
|
259
344
|
)
|
|
260
345
|
}>
|
|
261
|
-
|
|
346
|
+
{icon}
|
|
347
|
+
<span
|
|
348
|
+
id={valueId}
|
|
262
349
|
className={
|
|
263
350
|
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
|
-
)
|
|
351
|
+
searchAutocompleteStyles,
|
|
352
|
+
'mobile-value'
|
|
277
353
|
)
|
|
278
354
|
}>
|
|
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}
|
|
355
|
+
{children}
|
|
356
|
+
</span>
|
|
293
357
|
</div>
|
|
358
|
+
{validationState && !isDisabled ? validation : null}
|
|
359
|
+
{(inputValue !== '' || validationState != null) && !isReadOnly && clearButton}
|
|
294
360
|
</div>
|
|
295
|
-
</
|
|
361
|
+
</div>
|
|
296
362
|
);
|
|
297
363
|
});
|
|
298
364
|
|
|
299
|
-
interface SearchAutocompleteTrayProps extends SpectrumSearchAutocompleteProps<
|
|
300
|
-
state: ComboBoxState<
|
|
365
|
+
interface SearchAutocompleteTrayProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
366
|
+
state: ComboBoxState<T>,
|
|
301
367
|
overlayProps: HTMLAttributes<HTMLElement>,
|
|
302
368
|
loadingIndicator?: ReactElement,
|
|
303
369
|
onClose: () => void
|
|
304
370
|
}
|
|
305
371
|
|
|
306
|
-
function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
372
|
+
function SearchAutocompleteTray<T>(props: SearchAutocompleteTrayProps<T>) {
|
|
373
|
+
let searchIcon = (
|
|
374
|
+
<Magnifier data-testid="searchicon" />
|
|
375
|
+
);
|
|
376
|
+
|
|
307
377
|
let {
|
|
308
378
|
// completionMode = 'suggest',
|
|
309
379
|
state,
|
|
380
|
+
icon = searchIcon,
|
|
310
381
|
isDisabled,
|
|
311
382
|
validationState,
|
|
312
383
|
label,
|
|
@@ -317,37 +388,43 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
317
388
|
onSubmit
|
|
318
389
|
} = props;
|
|
319
390
|
|
|
320
|
-
let timeout = useRef(null);
|
|
391
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
321
392
|
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
|
-
|
|
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>(
|
|
329
401
|
{
|
|
330
402
|
...props,
|
|
331
|
-
|
|
403
|
+
layoutDelegate: layout,
|
|
332
404
|
popoverRef: popoverRef,
|
|
333
405
|
listBoxRef,
|
|
334
|
-
inputRef
|
|
406
|
+
inputRef,
|
|
407
|
+
// Handled outside the tray.
|
|
408
|
+
name: undefined
|
|
335
409
|
},
|
|
336
410
|
state
|
|
337
411
|
);
|
|
338
412
|
|
|
339
413
|
React.useEffect(() => {
|
|
340
|
-
|
|
414
|
+
if (inputRef.current) {
|
|
415
|
+
focusSafely(inputRef.current);
|
|
416
|
+
}
|
|
417
|
+
}, []);
|
|
341
418
|
|
|
342
|
-
|
|
419
|
+
React.useEffect(() => {
|
|
420
|
+
// When the tray closes, set state.isFocused (i.e. the tray input's focus tracker) to false.
|
|
343
421
|
// This is to prevent state.isFocused from being set to true when the tray closes via tapping on the underlay
|
|
344
422
|
// (FocusScope attempts to restore focus to the tray input when tapping outside the tray due to "contain")
|
|
345
423
|
// Have to do this manually since React doesn't call onBlur when a component is unmounted: https://github.com/facebook/react/issues/12363
|
|
346
|
-
|
|
424
|
+
if (!state.isOpen && state.isFocused) {
|
|
347
425
|
state.setFocused(false);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
}, []);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
351
428
|
|
|
352
429
|
let {dialogProps} = useDialog({
|
|
353
430
|
'aria-labelledby': useId(labelProps.id)
|
|
@@ -366,7 +443,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
366
443
|
<ClearButton
|
|
367
444
|
{...clearButtonProps}
|
|
368
445
|
preventFocus
|
|
369
|
-
aria-label={
|
|
446
|
+
aria-label={stringFormatter.format('clear')}
|
|
370
447
|
excludeFromTabOrder
|
|
371
448
|
UNSAFE_className={
|
|
372
449
|
classNames(
|
|
@@ -379,7 +456,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
379
456
|
|
|
380
457
|
let loadingCircle = (
|
|
381
458
|
<ProgressCircle
|
|
382
|
-
aria-label={
|
|
459
|
+
aria-label={stringFormatter.format('loading')}
|
|
383
460
|
size="S"
|
|
384
461
|
isIndeterminate
|
|
385
462
|
UNSAFE_className={classNames(
|
|
@@ -409,7 +486,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
409
486
|
return;
|
|
410
487
|
}
|
|
411
488
|
|
|
412
|
-
popoverRef.current
|
|
489
|
+
if (popoverRef.current) {
|
|
490
|
+
popoverRef.current.focus();
|
|
491
|
+
}
|
|
413
492
|
}, [inputRef, popoverRef, isTouchDown]);
|
|
414
493
|
|
|
415
494
|
let inputValue = inputProps.value;
|
|
@@ -432,8 +511,10 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
432
511
|
} else if (loadingState !== 'filtering') {
|
|
433
512
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
434
513
|
setShowLoading(false);
|
|
435
|
-
|
|
436
|
-
|
|
514
|
+
if (timeout.current !== null) {
|
|
515
|
+
clearTimeout(timeout.current);
|
|
516
|
+
timeout.current = null;
|
|
517
|
+
}
|
|
437
518
|
}
|
|
438
519
|
|
|
439
520
|
lastInputValue.current = inputValue;
|
|
@@ -442,25 +523,29 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
442
523
|
let onKeyDown = (e) => {
|
|
443
524
|
// Close virtual keyboard, close tray, and fire onSubmit if user hits Enter w/o any focused options
|
|
444
525
|
if (e.key === 'Enter' && state.selectionManager.focusedKey == null) {
|
|
445
|
-
popoverRef.current
|
|
446
|
-
onClose
|
|
447
|
-
|
|
526
|
+
popoverRef.current?.focus();
|
|
527
|
+
if (onClose) {
|
|
528
|
+
onClose();
|
|
529
|
+
}
|
|
530
|
+
if (onSubmit) {
|
|
531
|
+
onSubmit(inputValue == null ? null : inputValue.toString(), null);
|
|
532
|
+
}
|
|
448
533
|
} else {
|
|
449
|
-
inputProps.onKeyDown
|
|
534
|
+
if (inputProps.onKeyDown) {
|
|
535
|
+
inputProps.onKeyDown(e);
|
|
536
|
+
}
|
|
450
537
|
}
|
|
451
538
|
};
|
|
452
539
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
size: 'S'
|
|
463
|
-
});
|
|
540
|
+
if (icon) {
|
|
541
|
+
icon = React.cloneElement(icon, {
|
|
542
|
+
UNSAFE_className: classNames(
|
|
543
|
+
textfieldStyles,
|
|
544
|
+
'spectrum-Textfield-icon'
|
|
545
|
+
),
|
|
546
|
+
size: 'S'
|
|
547
|
+
});
|
|
548
|
+
}
|
|
464
549
|
|
|
465
550
|
return (
|
|
466
551
|
<FocusScope restoreFocus contain>
|
|
@@ -481,9 +566,9 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
481
566
|
inputRef={inputRef}
|
|
482
567
|
isDisabled={isDisabled}
|
|
483
568
|
isLoading={showLoading && loadingState === 'filtering'}
|
|
484
|
-
loadingIndicator={loadingState != null
|
|
569
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
485
570
|
validationState={validationState}
|
|
486
|
-
wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly
|
|
571
|
+
wrapperChildren={((state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly) ? clearButton : undefined}
|
|
487
572
|
icon={icon}
|
|
488
573
|
UNSAFE_className={
|
|
489
574
|
classNames(
|
|
@@ -492,8 +577,8 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
492
577
|
'spectrum-Textfield',
|
|
493
578
|
'spectrum-Search--loadable',
|
|
494
579
|
{
|
|
495
|
-
'spectrum-Search--invalid': validationState === 'invalid',
|
|
496
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
580
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
581
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
497
582
|
},
|
|
498
583
|
classNames(
|
|
499
584
|
searchAutocompleteStyles,
|
|
@@ -527,7 +612,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
527
612
|
shouldUseVirtualFocus
|
|
528
613
|
renderEmptyState={() => loadingState !== 'loading' && (
|
|
529
614
|
<span className={classNames(searchAutocompleteStyles, 'no-results')}>
|
|
530
|
-
{
|
|
615
|
+
{stringFormatter.format('noResults')}
|
|
531
616
|
</span>
|
|
532
617
|
)}
|
|
533
618
|
UNSAFE_className={
|
|
@@ -539,7 +624,7 @@ function SearchAutocompleteTray(props: SearchAutocompleteTrayProps) {
|
|
|
539
624
|
ref={listBoxRef}
|
|
540
625
|
onScroll={onScroll}
|
|
541
626
|
onLoadMore={onLoadMore}
|
|
542
|
-
isLoading={
|
|
627
|
+
isLoading={isLoading} />
|
|
543
628
|
<DismissButton onDismiss={onClose} />
|
|
544
629
|
</div>
|
|
545
630
|
</FocusScope>
|