@react-spectrum/autocomplete 3.0.0-alpha.2 → 3.0.0-alpha.20
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/import.mjs +1187 -0
- package/dist/main.css +1 -1
- package/dist/main.js +820 -640
- package/dist/main.js.map +1 -1
- package/dist/module.js +818 -622
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +34 -29
- package/src/MobileSearchAutocomplete.tsx +171 -130
- package/src/SearchAutocomplete.tsx +94 -67
- package/src/index.ts +2 -3
- package/src/searchautocomplete.css +11 -4
|
@@ -12,34 +12,46 @@
|
|
|
12
12
|
import {AriaButtonProps} from '@react-types/button';
|
|
13
13
|
import {classNames, useFocusableRef, useIsMobileDevice, useResizeObserver, useUnwrapDOMRef} from '@react-spectrum/utils';
|
|
14
14
|
import {ClearButton} from '@react-spectrum/button';
|
|
15
|
-
import {DismissButton, useOverlayPosition} from '@react-aria/overlays';
|
|
16
15
|
import {DOMRefValue, FocusableRef} from '@react-types/shared';
|
|
17
16
|
import {Field} from '@react-spectrum/label';
|
|
17
|
+
import {filterDOMProps, useLayoutEffect} from '@react-aria/utils';
|
|
18
18
|
import {FocusRing} from '@react-aria/focus';
|
|
19
19
|
// @ts-ignore
|
|
20
20
|
import intlMessages from '../intl/*.json';
|
|
21
21
|
import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
|
|
22
22
|
import Magnifier from '@spectrum-icons/ui/Magnifier';
|
|
23
23
|
import {MobileSearchAutocomplete} from './MobileSearchAutocomplete';
|
|
24
|
-
import {Placement} from '@react-types/overlays';
|
|
25
24
|
import {Popover} from '@react-spectrum/overlays';
|
|
26
25
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
27
|
-
import React, {
|
|
26
|
+
import React, {
|
|
27
|
+
forwardRef,
|
|
28
|
+
InputHTMLAttributes,
|
|
29
|
+
ReactElement,
|
|
30
|
+
RefObject,
|
|
31
|
+
useCallback,
|
|
32
|
+
useEffect,
|
|
33
|
+
useRef,
|
|
34
|
+
useState
|
|
35
|
+
} from 'react';
|
|
36
|
+
import searchAutocompleteStyles from './searchautocomplete.css';
|
|
28
37
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
29
38
|
import {SpectrumSearchAutocompleteProps} from '@react-types/autocomplete';
|
|
30
39
|
import styles from '@adobe/spectrum-css-temp/components/inputgroup/vars.css';
|
|
31
40
|
import {TextFieldBase} from '@react-spectrum/textfield';
|
|
32
41
|
import textfieldStyles from '@adobe/spectrum-css-temp/components/textfield/vars.css';
|
|
33
42
|
import {useComboBoxState} from '@react-stately/combobox';
|
|
34
|
-
import {useFilter,
|
|
43
|
+
import {useFilter, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
35
44
|
import {useHover} from '@react-aria/interactions';
|
|
36
|
-
import {useLayoutEffect} from '@react-aria/utils';
|
|
37
45
|
import {useProvider, useProviderProps} from '@react-spectrum/provider';
|
|
38
46
|
import {useSearchAutocomplete} from '@react-aria/autocomplete';
|
|
39
47
|
|
|
40
48
|
function SearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
41
49
|
props = useProviderProps(props);
|
|
42
50
|
|
|
51
|
+
if (props.placeholder) {
|
|
52
|
+
console.warn('Placeholders are deprecated due to accessibility issues. Please use help text instead.');
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
let isMobile = useIsMobileDevice();
|
|
44
56
|
if (isMobile) {
|
|
45
57
|
// menuTrigger=focus/manual don't apply to mobile searchwithin
|
|
@@ -49,7 +61,7 @@ function SearchAutocomplete<T extends object>(props: SpectrumSearchAutocompleteP
|
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocompleteProps<T>, ref: FocusableRef<HTMLElement>) {
|
|
53
65
|
props = useProviderProps(props);
|
|
54
66
|
|
|
55
67
|
let {
|
|
@@ -62,12 +74,12 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
62
74
|
onSubmit = () => {}
|
|
63
75
|
} = props;
|
|
64
76
|
|
|
65
|
-
let
|
|
77
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
66
78
|
let isAsync = loadingState != null;
|
|
67
|
-
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>();
|
|
79
|
+
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>(null);
|
|
68
80
|
let unwrappedPopoverRef = useUnwrapDOMRef(popoverRef);
|
|
69
|
-
let listBoxRef = useRef();
|
|
70
|
-
let inputRef = useRef<HTMLInputElement>();
|
|
81
|
+
let listBoxRef = useRef(null);
|
|
82
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
71
83
|
let domRef = useFocusableRef(ref, inputRef);
|
|
72
84
|
|
|
73
85
|
let {contains} = useFilter({sensitivity: 'base'});
|
|
@@ -82,9 +94,9 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
82
94
|
defaultSelectedKey: undefined
|
|
83
95
|
}
|
|
84
96
|
);
|
|
85
|
-
let layout = useListBoxLayout(state);
|
|
86
|
-
|
|
87
|
-
let {inputProps, listBoxProps, labelProps, clearButtonProps} = useSearchAutocomplete(
|
|
97
|
+
let layout = useListBoxLayout(state, loadingState === 'loadingMore');
|
|
98
|
+
|
|
99
|
+
let {inputProps, listBoxProps, labelProps, clearButtonProps, descriptionProps, errorMessageProps} = useSearchAutocomplete(
|
|
88
100
|
{
|
|
89
101
|
...props,
|
|
90
102
|
keyboardDelegate: layout,
|
|
@@ -96,18 +108,8 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
96
108
|
state
|
|
97
109
|
);
|
|
98
110
|
|
|
99
|
-
let {overlayProps, placement, updatePosition} = useOverlayPosition({
|
|
100
|
-
targetRef: inputRef,
|
|
101
|
-
overlayRef: unwrappedPopoverRef,
|
|
102
|
-
scrollRef: listBoxRef,
|
|
103
|
-
placement: `${direction} end` as Placement,
|
|
104
|
-
shouldFlip: shouldFlip,
|
|
105
|
-
isOpen: state.isOpen,
|
|
106
|
-
onClose: state.close
|
|
107
|
-
});
|
|
108
|
-
|
|
109
111
|
// Measure the width of the inputfield to inform the width of the menu (below).
|
|
110
|
-
let [menuWidth, setMenuWidth] = useState(
|
|
112
|
+
let [menuWidth, setMenuWidth] = useState<number>(0);
|
|
111
113
|
let {scale} = useProvider();
|
|
112
114
|
|
|
113
115
|
let onResize = useCallback(() => {
|
|
@@ -124,26 +126,19 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
124
126
|
|
|
125
127
|
useLayoutEffect(onResize, [scale, onResize]);
|
|
126
128
|
|
|
127
|
-
// Update position once the ListBox has rendered. This ensures that
|
|
128
|
-
// it flips properly when it doesn't fit in the available space.
|
|
129
|
-
// TODO: add ResizeObserver to useOverlayPosition so we don't need this.
|
|
130
|
-
useLayoutEffect(() => {
|
|
131
|
-
if (state.isOpen) {
|
|
132
|
-
requestAnimationFrame(() => {
|
|
133
|
-
updatePosition();
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}, [state.isOpen, updatePosition]);
|
|
137
|
-
|
|
138
129
|
let style = {
|
|
139
|
-
|
|
140
|
-
width: isQuiet ? null : menuWidth,
|
|
130
|
+
width: isQuiet ? undefined : menuWidth,
|
|
141
131
|
minWidth: isQuiet ? `calc(${menuWidth}px + calc(2 * var(--spectrum-dropdown-quiet-offset)))` : menuWidth
|
|
142
132
|
};
|
|
143
133
|
|
|
144
134
|
return (
|
|
145
135
|
<>
|
|
146
|
-
<Field
|
|
136
|
+
<Field
|
|
137
|
+
{...props}
|
|
138
|
+
descriptionProps={descriptionProps}
|
|
139
|
+
errorMessageProps={errorMessageProps}
|
|
140
|
+
labelProps={labelProps}
|
|
141
|
+
ref={domRef}>
|
|
147
142
|
<SearchAutocompleteInput
|
|
148
143
|
{...props}
|
|
149
144
|
isOpen={state.isOpen}
|
|
@@ -153,14 +148,15 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
153
148
|
clearButtonProps={clearButtonProps} />
|
|
154
149
|
</Field>
|
|
155
150
|
<Popover
|
|
156
|
-
|
|
151
|
+
state={state}
|
|
157
152
|
UNSAFE_style={style}
|
|
158
153
|
UNSAFE_className={classNames(styles, 'spectrum-InputGroup-popover', {'spectrum-InputGroup-popover--quiet': isQuiet})}
|
|
159
154
|
ref={popoverRef}
|
|
160
|
-
|
|
155
|
+
triggerRef={inputRef}
|
|
156
|
+
placement={`${direction} end`}
|
|
161
157
|
hideArrow
|
|
162
158
|
isNonModal
|
|
163
|
-
|
|
159
|
+
shouldFlip={shouldFlip}>
|
|
164
160
|
<ListBoxBase
|
|
165
161
|
{...listBoxProps}
|
|
166
162
|
ref={listBoxRef}
|
|
@@ -171,30 +167,39 @@ const SearchAutocompleteBase = React.forwardRef(function SearchAutocompleteBase<
|
|
|
171
167
|
layout={layout}
|
|
172
168
|
state={state}
|
|
173
169
|
shouldUseVirtualFocus
|
|
174
|
-
isLoading={loadingState === 'loadingMore'}
|
|
170
|
+
isLoading={loadingState === 'loading' || loadingState === 'loadingMore'}
|
|
175
171
|
onLoadMore={onLoadMore}
|
|
176
172
|
renderEmptyState={() => isAsync && (
|
|
177
|
-
<span>
|
|
178
|
-
{
|
|
173
|
+
<span className={classNames(searchAutocompleteStyles, 'no-results')}>
|
|
174
|
+
{stringFormatter.format('noResults')}
|
|
179
175
|
</span>
|
|
180
176
|
)} />
|
|
181
|
-
<DismissButton onDismiss={() => state.close()} />
|
|
182
177
|
</Popover>
|
|
183
178
|
</>
|
|
184
179
|
);
|
|
185
|
-
}
|
|
180
|
+
}
|
|
186
181
|
|
|
187
|
-
|
|
182
|
+
let SearchAutocompleteBase = React.forwardRef(_SearchAutocompleteBase) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
interface SearchAutocompleteInputProps<T> extends SpectrumSearchAutocompleteProps<T> {
|
|
188
186
|
inputProps: InputHTMLAttributes<HTMLInputElement>,
|
|
189
|
-
inputRef: RefObject<HTMLInputElement
|
|
187
|
+
inputRef: RefObject<HTMLInputElement>,
|
|
190
188
|
style?: React.CSSProperties,
|
|
191
189
|
className?: string,
|
|
192
190
|
isOpen?: boolean,
|
|
193
191
|
clearButtonProps: AriaButtonProps
|
|
194
192
|
}
|
|
195
193
|
|
|
196
|
-
|
|
194
|
+
// any type is because we don't want to call useObjectRef because this is an internal component and we know
|
|
195
|
+
// we are always passing an object ref
|
|
196
|
+
function _SearchAutocompleteInput<T>(props: SearchAutocompleteInputProps<T>, ref: any) {
|
|
197
|
+
let searchIcon = (
|
|
198
|
+
<Magnifier data-testid="searchicon" />
|
|
199
|
+
);
|
|
200
|
+
|
|
197
201
|
let {
|
|
202
|
+
icon = searchIcon,
|
|
198
203
|
isQuiet,
|
|
199
204
|
isDisabled,
|
|
200
205
|
isReadOnly,
|
|
@@ -210,13 +215,14 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
210
215
|
clearButtonProps
|
|
211
216
|
} = props;
|
|
212
217
|
let {hoverProps, isHovered} = useHover({});
|
|
213
|
-
let
|
|
214
|
-
let
|
|
218
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
219
|
+
let domProps = filterDOMProps(props);
|
|
220
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
215
221
|
let [showLoading, setShowLoading] = useState(false);
|
|
216
222
|
|
|
217
223
|
let loadingCircle = (
|
|
218
224
|
<ProgressCircle
|
|
219
|
-
aria-label={
|
|
225
|
+
aria-label={stringFormatter.format('loading')}
|
|
220
226
|
size="S"
|
|
221
227
|
isIndeterminate
|
|
222
228
|
UNSAFE_className={classNames(
|
|
@@ -225,14 +231,14 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
225
231
|
classNames(
|
|
226
232
|
styles,
|
|
227
233
|
'spectrum-InputGroup-input-circleLoader'
|
|
234
|
+
),
|
|
235
|
+
classNames(
|
|
236
|
+
searchStyles,
|
|
237
|
+
'spectrum-Search-circleLoader'
|
|
228
238
|
)
|
|
229
239
|
)} />
|
|
230
240
|
);
|
|
231
241
|
|
|
232
|
-
let searchIcon = (
|
|
233
|
-
<Magnifier data-testid="searchicon" />
|
|
234
|
-
);
|
|
235
|
-
|
|
236
242
|
let clearButton = (
|
|
237
243
|
<ClearButton
|
|
238
244
|
{...clearButtonProps}
|
|
@@ -267,8 +273,10 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
267
273
|
} else if (!isLoading) {
|
|
268
274
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
269
275
|
setShowLoading(false);
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
if (timeout.current != null) {
|
|
277
|
+
clearTimeout(timeout.current);
|
|
278
|
+
timeout.current = null;
|
|
279
|
+
}
|
|
272
280
|
}
|
|
273
281
|
|
|
274
282
|
lastInputValue.current = inputValue;
|
|
@@ -292,44 +300,63 @@ const SearchAutocompleteInput = React.forwardRef(function SearchAutocompleteInpu
|
|
|
292
300
|
{
|
|
293
301
|
'spectrum-InputGroup--quiet': isQuiet,
|
|
294
302
|
'is-disabled': isDisabled,
|
|
295
|
-
'spectrum-InputGroup--invalid': validationState === 'invalid',
|
|
303
|
+
'spectrum-InputGroup--invalid': validationState === 'invalid' && !isDisabled,
|
|
296
304
|
'is-hovered': isHovered
|
|
297
305
|
},
|
|
306
|
+
classNames(
|
|
307
|
+
searchAutocompleteStyles,
|
|
308
|
+
'searchautocomplete'
|
|
309
|
+
),
|
|
298
310
|
className
|
|
299
311
|
)
|
|
300
312
|
}>
|
|
301
313
|
<TextFieldBase
|
|
314
|
+
{...domProps}
|
|
302
315
|
inputProps={inputProps}
|
|
303
316
|
inputRef={inputRef}
|
|
304
317
|
UNSAFE_className={
|
|
305
318
|
classNames(
|
|
306
319
|
searchStyles,
|
|
307
320
|
'spectrum-Search',
|
|
321
|
+
'spectrum-Search--loadable',
|
|
308
322
|
'spectrum-Textfield',
|
|
309
323
|
{
|
|
310
324
|
'is-disabled': isDisabled,
|
|
311
325
|
'is-quiet': isQuiet,
|
|
312
|
-
'spectrum-Search--invalid': validationState === 'invalid',
|
|
313
|
-
'spectrum-Search--valid': validationState === 'valid'
|
|
314
|
-
}
|
|
326
|
+
'spectrum-Search--invalid': validationState === 'invalid' && !isDisabled,
|
|
327
|
+
'spectrum-Search--valid': validationState === 'valid' && !isDisabled
|
|
328
|
+
},
|
|
329
|
+
classNames(
|
|
330
|
+
styles,
|
|
331
|
+
'spectrum-InputGroup-field'
|
|
332
|
+
)
|
|
315
333
|
)
|
|
316
334
|
}
|
|
317
335
|
inputClassName={classNames(searchStyles, 'spectrum-Search-input')}
|
|
336
|
+
validationIconClassName={
|
|
337
|
+
classNames(
|
|
338
|
+
searchStyles,
|
|
339
|
+
'spectrum-Search-validationIcon'
|
|
340
|
+
)
|
|
341
|
+
}
|
|
318
342
|
isDisabled={isDisabled}
|
|
319
343
|
isQuiet={isQuiet}
|
|
320
344
|
validationState={validationState}
|
|
321
345
|
isLoading={showLoading && (isOpen || menuTrigger === 'manual' || loadingState === 'loading')}
|
|
322
|
-
loadingIndicator={loadingState != null
|
|
323
|
-
icon={
|
|
324
|
-
wrapperChildren={(inputValue !== '' && !isReadOnly
|
|
346
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
347
|
+
icon={icon}
|
|
348
|
+
wrapperChildren={(inputValue !== '' || loadingState === 'filtering' || validationState != null) && !isReadOnly ? clearButton : undefined}
|
|
349
|
+
disableFocusRing />
|
|
325
350
|
</div>
|
|
326
351
|
</FocusRing>
|
|
327
352
|
);
|
|
328
|
-
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let SearchAutocompleteInput = React.forwardRef(_SearchAutocompleteInput) as <T>(props: SearchAutocompleteInputProps<T> & {ref?: any}) => ReactElement;
|
|
329
356
|
|
|
330
357
|
|
|
331
358
|
/**
|
|
332
359
|
* A SearchAutocomplete is a searchfield that supports a dynamic list of suggestions.
|
|
333
360
|
*/
|
|
334
|
-
let _SearchAutocomplete = forwardRef(SearchAutocomplete);
|
|
361
|
+
let _SearchAutocomplete = forwardRef(SearchAutocomplete) as <T>(props: SpectrumSearchAutocompleteProps<T> & {ref?: FocusableRef<HTMLElement>}) => ReactElement;
|
|
335
362
|
export {_SearchAutocomplete as SearchAutocomplete};
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
12
|
/// <reference types="css-module-types" />
|
|
14
|
-
|
|
15
|
-
export * from './SearchAutocomplete';
|
|
13
|
+
export {SearchAutocomplete} from './SearchAutocomplete';
|
|
16
14
|
export {Item, Section} from '@react-stately/collections';
|
|
15
|
+
export type {SpectrumSearchAutocompleteProps} from '@react-types/autocomplete';
|
|
@@ -23,18 +23,25 @@
|
|
|
23
23
|
font-style: italic;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/* override .spectrum-InputGroup */
|
|
27
|
+
.searchautocomplete.searchautocomplete {
|
|
28
|
+
border-radius: var(--spectrum-search-border-radius);
|
|
29
|
+
--spectrum-focus-ring-border-radius: var(--spectrum-search-border-radius);
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
.mobile-searchautocomplete {
|
|
27
33
|
outline: none;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
.mobile-input {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
text-overflow: ellipsis;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
.mobile-value {
|
|
37
|
-
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
.tray-dialog {
|