@react-spectrum/combobox 3.13.4 → 3.14.0
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/ComboBox.main.js +18 -14
- package/dist/ComboBox.main.js.map +1 -1
- package/dist/ComboBox.mjs +18 -14
- package/dist/ComboBox.module.js +18 -14
- package/dist/ComboBox.module.js.map +1 -1
- package/dist/MobileComboBox.main.js +31 -22
- package/dist/MobileComboBox.main.js.map +1 -1
- package/dist/MobileComboBox.mjs +33 -24
- package/dist/MobileComboBox.module.js +33 -24
- package/dist/MobileComboBox.module.js.map +1 -1
- package/dist/button_vars_css.main.js.map +1 -1
- package/dist/button_vars_css.module.js.map +1 -1
- package/dist/fieldlabel_vars_css.main.js.map +1 -1
- package/dist/fieldlabel_vars_css.module.js.map +1 -1
- package/dist/inputgroup_vars_css.main.js.map +1 -1
- package/dist/inputgroup_vars_css.module.js.map +1 -1
- package/dist/search_vars_css.main.js.map +1 -1
- package/dist/search_vars_css.module.js.map +1 -1
- package/dist/textfield_vars_css.main.js.map +1 -1
- package/dist/textfield_vars_css.module.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +28 -28
- package/src/ComboBox.tsx +22 -16
- package/src/MobileComboBox.tsx +33 -28
package/src/ComboBox.tsx
CHANGED
|
@@ -33,6 +33,7 @@ import {Popover} from '@react-spectrum/overlays';
|
|
|
33
33
|
import {PressResponder, useHover} from '@react-aria/interactions';
|
|
34
34
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
35
35
|
import React, {
|
|
36
|
+
ForwardedRef,
|
|
36
37
|
InputHTMLAttributes,
|
|
37
38
|
ReactElement,
|
|
38
39
|
RefObject,
|
|
@@ -69,7 +70,7 @@ function ComboBox<T extends object>(props: SpectrumComboBoxProps<T>, ref: Focusa
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
const ComboBoxBase = React.forwardRef(function ComboBoxBase
|
|
73
|
+
const ComboBoxBase = React.forwardRef(function ComboBoxBase(props: SpectrumComboBoxProps<any>, ref: FocusableRef<HTMLElement>) {
|
|
73
74
|
let {
|
|
74
75
|
menuTrigger = 'input',
|
|
75
76
|
shouldFlip = true,
|
|
@@ -89,14 +90,14 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
|
|
|
89
90
|
|
|
90
91
|
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/combobox');
|
|
91
92
|
let isAsync = loadingState != null;
|
|
92
|
-
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>(
|
|
93
|
+
let popoverRef = useRef<DOMRefValue<HTMLDivElement>>(null);
|
|
93
94
|
let unwrappedPopoverRef = useUnwrapDOMRef(popoverRef);
|
|
94
|
-
let buttonRef = useRef<FocusableRefValue<HTMLElement>>(
|
|
95
|
+
let buttonRef = useRef<FocusableRefValue<HTMLElement>>(null);
|
|
95
96
|
let unwrappedButtonRef = useUnwrapDOMRef(buttonRef);
|
|
96
|
-
let listBoxRef = useRef(
|
|
97
|
-
let inputRef = useRef<HTMLInputElement>(
|
|
97
|
+
let listBoxRef = useRef(null);
|
|
98
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
98
99
|
// serve as the new popover `triggerRef` instead of `unwrappedButtonRef` before for better positioning.
|
|
99
|
-
let inputGroupRef = useRef<HTMLDivElement>(
|
|
100
|
+
let inputGroupRef = useRef<HTMLDivElement>(null);
|
|
100
101
|
let domRef = useFocusableRef(ref, inputRef);
|
|
101
102
|
|
|
102
103
|
let {contains} = useFilter({sensitivity: 'base'});
|
|
@@ -124,7 +125,7 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
|
|
|
124
125
|
);
|
|
125
126
|
|
|
126
127
|
// Measure the width of the inputfield and the button to inform the width of the menu (below).
|
|
127
|
-
let [menuWidth, setMenuWidth] = useState(
|
|
128
|
+
let [menuWidth, setMenuWidth] = useState<number | undefined>(undefined);
|
|
128
129
|
let {scale} = useProvider();
|
|
129
130
|
|
|
130
131
|
let onResize = useCallback(() => {
|
|
@@ -142,11 +143,12 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
|
|
|
142
143
|
|
|
143
144
|
useLayoutEffect(onResize, [scale, onResize]);
|
|
144
145
|
|
|
145
|
-
let width = isQuiet ?
|
|
146
|
+
let width = isQuiet ? undefined : menuWidth;
|
|
146
147
|
let style = {
|
|
147
148
|
width: customMenuWidth ? dimensionValue(customMenuWidth) : width,
|
|
148
149
|
minWidth: isQuiet ? `calc(${menuWidth}px + calc(2 * var(--spectrum-dropdown-quiet-offset)))` : menuWidth
|
|
149
150
|
};
|
|
151
|
+
let cbInputProps = {...props, children: null};
|
|
150
152
|
|
|
151
153
|
return (
|
|
152
154
|
<>
|
|
@@ -160,14 +162,14 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
|
|
|
160
162
|
labelProps={labelProps}
|
|
161
163
|
ref={domRef}>
|
|
162
164
|
<ComboBoxInput
|
|
163
|
-
{...
|
|
165
|
+
{...cbInputProps}
|
|
164
166
|
isOpen={state.isOpen}
|
|
165
167
|
loadingState={loadingState}
|
|
166
168
|
inputProps={inputProps}
|
|
167
169
|
inputRef={inputRef}
|
|
168
170
|
triggerProps={buttonProps}
|
|
169
171
|
triggerRef={buttonRef}
|
|
170
|
-
validationState={props.validationState || (isInvalid ? 'invalid' :
|
|
172
|
+
validationState={props.validationState || (isInvalid ? 'invalid' : undefined)}
|
|
171
173
|
ref={inputGroupRef} />
|
|
172
174
|
</Field>
|
|
173
175
|
{name && formValue === 'key' && <input type="hidden" name={name} value={state.selectedKey ?? ''} />}
|
|
@@ -186,7 +188,7 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
|
|
|
186
188
|
{...listBoxProps}
|
|
187
189
|
ref={listBoxRef}
|
|
188
190
|
disallowEmptySelection
|
|
189
|
-
autoFocus={state.focusStrategy}
|
|
191
|
+
autoFocus={state.focusStrategy ?? undefined}
|
|
190
192
|
shouldSelectOnPressUp
|
|
191
193
|
focusOnPointerEnter
|
|
192
194
|
layout={layout}
|
|
@@ -215,7 +217,7 @@ interface ComboBoxInputProps extends SpectrumComboBoxProps<unknown> {
|
|
|
215
217
|
isOpen?: boolean
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
const ComboBoxInput = React.forwardRef(function ComboBoxInput(props: ComboBoxInputProps, ref:
|
|
220
|
+
const ComboBoxInput = React.forwardRef(function ComboBoxInput(props: ComboBoxInputProps, ref: ForwardedRef<HTMLElement | null>) {
|
|
219
221
|
let {
|
|
220
222
|
isQuiet,
|
|
221
223
|
isDisabled,
|
|
@@ -233,7 +235,7 @@ const ComboBoxInput = React.forwardRef(function ComboBoxInput(props: ComboBoxInp
|
|
|
233
235
|
} = props;
|
|
234
236
|
let {hoverProps, isHovered} = useHover({});
|
|
235
237
|
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/combobox');
|
|
236
|
-
let timeout = useRef(null);
|
|
238
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
237
239
|
let [showLoading, setShowLoading] = useState(false);
|
|
238
240
|
|
|
239
241
|
let loadingCircle = (
|
|
@@ -272,7 +274,9 @@ const ComboBoxInput = React.forwardRef(function ComboBoxInput(props: ComboBoxInp
|
|
|
272
274
|
} else if (!isLoading) {
|
|
273
275
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
274
276
|
setShowLoading(false);
|
|
275
|
-
|
|
277
|
+
if (timeout.current) {
|
|
278
|
+
clearTimeout(timeout.current);
|
|
279
|
+
}
|
|
276
280
|
timeout.current = null;
|
|
277
281
|
}
|
|
278
282
|
|
|
@@ -281,7 +285,9 @@ const ComboBoxInput = React.forwardRef(function ComboBoxInput(props: ComboBoxInp
|
|
|
281
285
|
|
|
282
286
|
useEffect(() => {
|
|
283
287
|
return () => {
|
|
284
|
-
|
|
288
|
+
if (timeout.current) {
|
|
289
|
+
clearTimeout(timeout.current);
|
|
290
|
+
}
|
|
285
291
|
timeout.current = null;
|
|
286
292
|
};
|
|
287
293
|
}, []);
|
|
@@ -337,7 +343,7 @@ const ComboBoxInput = React.forwardRef(function ComboBoxInput(props: ComboBoxInp
|
|
|
337
343
|
// loading circle should only be displayed if menu is open, if menuTrigger is "manual", or first time load (to stop circle from showing up when user selects an option)
|
|
338
344
|
// TODO: add special case for completionMode: complete as well
|
|
339
345
|
isLoading={showLoading && (isOpen || menuTrigger === 'manual' || loadingState === 'loading')}
|
|
340
|
-
loadingIndicator={loadingState != null
|
|
346
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
341
347
|
disableFocusRing />
|
|
342
348
|
<PressResponder preventFocusOnPress isPressed={isOpen}>
|
|
343
349
|
<FieldButton
|
package/src/MobileComboBox.tsx
CHANGED
|
@@ -21,15 +21,15 @@ import {ComboBoxState, useComboBoxState} from '@react-stately/combobox';
|
|
|
21
21
|
import comboboxStyles from './combobox.css';
|
|
22
22
|
import {DismissButton, useOverlayTrigger} from '@react-aria/overlays';
|
|
23
23
|
import {Field} from '@react-spectrum/label';
|
|
24
|
-
import {FocusableRef, FocusableRefValue,
|
|
24
|
+
import {FocusableRef, FocusableRefValue, ValidationState} from '@react-types/shared';
|
|
25
25
|
import {FocusRing, focusSafely, FocusScope} from '@react-aria/focus';
|
|
26
26
|
// @ts-ignore
|
|
27
27
|
import intlMessages from '../intl/*.json';
|
|
28
28
|
import labelStyles from '@adobe/spectrum-css-temp/components/fieldlabel/vars.css';
|
|
29
29
|
import {ListBoxBase, useListBoxLayout} from '@react-spectrum/listbox';
|
|
30
|
-
import {mergeProps, useFormReset, useId} from '@react-aria/utils';
|
|
30
|
+
import {mergeProps, useFormReset, useId, useObjectRef} from '@react-aria/utils';
|
|
31
31
|
import {ProgressCircle} from '@react-spectrum/progress';
|
|
32
|
-
import React, {HTMLAttributes, InputHTMLAttributes, ReactElement, ReactNode, useCallback, useEffect, useRef, useState} from 'react';
|
|
32
|
+
import React, {ForwardedRef, HTMLAttributes, InputHTMLAttributes, ReactElement, ReactNode, useCallback, useEffect, useRef, useState} from 'react';
|
|
33
33
|
import searchStyles from '@adobe/spectrum-css-temp/components/search/vars.css';
|
|
34
34
|
import {setInteractionModality, useHover} from '@react-aria/interactions';
|
|
35
35
|
import {SpectrumComboBoxProps} from '@react-types/combobox';
|
|
@@ -45,7 +45,7 @@ import {useFilter, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
|
45
45
|
import {useFormValidation} from '@react-aria/form';
|
|
46
46
|
import {useProviderProps} from '@react-spectrum/provider';
|
|
47
47
|
|
|
48
|
-
export const MobileComboBox = React.forwardRef(function MobileComboBox
|
|
48
|
+
export const MobileComboBox = React.forwardRef(function MobileComboBox(props: SpectrumComboBoxProps<any>, ref: FocusableRef<HTMLElement>) {
|
|
49
49
|
props = useProviderProps(props);
|
|
50
50
|
|
|
51
51
|
let {
|
|
@@ -73,17 +73,17 @@ export const MobileComboBox = React.forwardRef(function MobileComboBox<T extends
|
|
|
73
73
|
shouldCloseOnBlur: false
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
let buttonRef = useRef<
|
|
76
|
+
let buttonRef = useRef<HTMLDivElement>(null);
|
|
77
77
|
let domRef = useFocusableRef(ref, buttonRef);
|
|
78
78
|
let {triggerProps, overlayProps} = useOverlayTrigger({type: 'listbox'}, state, buttonRef);
|
|
79
79
|
|
|
80
80
|
let inputRef = useRef<HTMLInputElement>(null);
|
|
81
81
|
useFormValidation({
|
|
82
82
|
...props,
|
|
83
|
-
focus: () => buttonRef.current
|
|
83
|
+
focus: () => buttonRef.current?.focus()
|
|
84
84
|
}, state, inputRef);
|
|
85
85
|
let {isInvalid, validationErrors, validationDetails} = state.displayValidation;
|
|
86
|
-
let validationState = props.validationState || (isInvalid ? 'invalid' :
|
|
86
|
+
let validationState = props.validationState || (isInvalid ? 'invalid' : undefined);
|
|
87
87
|
let errorMessage = props.errorMessage ?? validationErrors.join(' ');
|
|
88
88
|
|
|
89
89
|
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
|
|
@@ -96,7 +96,7 @@ export const MobileComboBox = React.forwardRef(function MobileComboBox<T extends
|
|
|
96
96
|
// Focus the button and show focus ring when clicking on the label
|
|
97
97
|
labelProps.onClick = () => {
|
|
98
98
|
if (!props.isDisabled) {
|
|
99
|
-
buttonRef.current
|
|
99
|
+
buttonRef.current?.focus();
|
|
100
100
|
setInteractionModality('keyboard');
|
|
101
101
|
}
|
|
102
102
|
};
|
|
@@ -104,7 +104,7 @@ export const MobileComboBox = React.forwardRef(function MobileComboBox<T extends
|
|
|
104
104
|
let inputProps: InputHTMLAttributes<HTMLInputElement> = {
|
|
105
105
|
type: 'hidden',
|
|
106
106
|
name,
|
|
107
|
-
value: formValue === 'text' ? state.inputValue : state.selectedKey
|
|
107
|
+
value: formValue === 'text' ? state.inputValue : String(state.selectedKey)
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
if (validationBehavior === 'native') {
|
|
@@ -117,7 +117,7 @@ export const MobileComboBox = React.forwardRef(function MobileComboBox<T extends
|
|
|
117
117
|
inputProps.onChange = () => {};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
useFormReset(inputRef, inputProps.value, formValue === 'text' ? state.setInputValue : state.setSelectedKey);
|
|
120
|
+
useFormReset(inputRef, String(inputProps.value ?? ''), formValue === 'text' ? state.setInputValue : state.setSelectedKey);
|
|
121
121
|
|
|
122
122
|
return (
|
|
123
123
|
<>
|
|
@@ -166,7 +166,7 @@ interface ComboBoxButtonProps extends AriaButtonProps {
|
|
|
166
166
|
className?: string
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
const ComboBoxButton = React.forwardRef(function ComboBoxButton(props: ComboBoxButtonProps, ref:
|
|
169
|
+
export const ComboBoxButton = React.forwardRef(function ComboBoxButton(props: ComboBoxButtonProps, ref: ForwardedRef<HTMLDivElement>) {
|
|
170
170
|
let {
|
|
171
171
|
isQuiet,
|
|
172
172
|
isDisabled,
|
|
@@ -194,6 +194,7 @@ const ComboBoxButton = React.forwardRef(function ComboBoxButton(props: ComboBoxB
|
|
|
194
194
|
)
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
let objRef = useObjectRef(ref);
|
|
197
198
|
let {hoverProps, isHovered} = useHover({});
|
|
198
199
|
let {buttonProps, isPressed} = useButton({
|
|
199
200
|
...props,
|
|
@@ -204,7 +205,7 @@ const ComboBoxButton = React.forwardRef(function ComboBoxButton(props: ComboBoxB
|
|
|
204
205
|
validationState === 'invalid' ? invalidId : null
|
|
205
206
|
].filter(Boolean).join(' '),
|
|
206
207
|
elementType: 'div'
|
|
207
|
-
},
|
|
208
|
+
}, objRef);
|
|
208
209
|
|
|
209
210
|
return (
|
|
210
211
|
(<FocusRing
|
|
@@ -213,7 +214,7 @@ const ComboBoxButton = React.forwardRef(function ComboBoxButton(props: ComboBoxB
|
|
|
213
214
|
<div
|
|
214
215
|
{...mergeProps(hoverProps, buttonProps)}
|
|
215
216
|
aria-haspopup="dialog"
|
|
216
|
-
ref={
|
|
217
|
+
ref={objRef}
|
|
217
218
|
style={{...style, outline: 'none'}}
|
|
218
219
|
className={
|
|
219
220
|
classNames(
|
|
@@ -307,8 +308,8 @@ const ComboBoxButton = React.forwardRef(function ComboBoxButton(props: ComboBoxB
|
|
|
307
308
|
);
|
|
308
309
|
});
|
|
309
310
|
|
|
310
|
-
interface ComboBoxTrayProps extends SpectrumComboBoxProps<
|
|
311
|
-
state: ComboBoxState<
|
|
311
|
+
interface ComboBoxTrayProps extends SpectrumComboBoxProps<any> {
|
|
312
|
+
state: ComboBoxState<any>,
|
|
312
313
|
overlayProps: HTMLAttributes<HTMLElement>,
|
|
313
314
|
loadingIndicator?: ReactElement,
|
|
314
315
|
onClose: () => void
|
|
@@ -327,12 +328,12 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
327
328
|
onClose
|
|
328
329
|
} = props;
|
|
329
330
|
|
|
330
|
-
let timeout = useRef(null);
|
|
331
|
+
let timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
331
332
|
let [showLoading, setShowLoading] = useState(false);
|
|
332
|
-
let inputRef = useRef<HTMLInputElement>(
|
|
333
|
-
let buttonRef = useRef<FocusableRefValue<HTMLElement>>(
|
|
334
|
-
let popoverRef = useRef<HTMLDivElement>(
|
|
335
|
-
let listBoxRef = useRef<HTMLDivElement>(
|
|
333
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
334
|
+
let buttonRef = useRef<FocusableRefValue<HTMLElement>>(null);
|
|
335
|
+
let popoverRef = useRef<HTMLDivElement>(null);
|
|
336
|
+
let listBoxRef = useRef<HTMLDivElement>(null);
|
|
336
337
|
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
|
|
337
338
|
let layout = useListBoxLayout();
|
|
338
339
|
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/combobox');
|
|
@@ -353,7 +354,9 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
353
354
|
);
|
|
354
355
|
|
|
355
356
|
React.useEffect(() => {
|
|
356
|
-
|
|
357
|
+
if (inputRef.current) {
|
|
358
|
+
focusSafely(inputRef.current);
|
|
359
|
+
}
|
|
357
360
|
}, []);
|
|
358
361
|
|
|
359
362
|
React.useEffect(() => {
|
|
@@ -388,7 +391,7 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
388
391
|
excludeFromTabOrder
|
|
389
392
|
onPress={() => {
|
|
390
393
|
state.setInputValue('');
|
|
391
|
-
inputRef.current
|
|
394
|
+
inputRef.current?.focus();
|
|
392
395
|
}}
|
|
393
396
|
UNSAFE_className={
|
|
394
397
|
classNames(
|
|
@@ -431,7 +434,7 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
431
434
|
return;
|
|
432
435
|
}
|
|
433
436
|
|
|
434
|
-
popoverRef.current
|
|
437
|
+
popoverRef.current?.focus();
|
|
435
438
|
}, [inputRef, popoverRef, isTouchDown]);
|
|
436
439
|
|
|
437
440
|
let inputValue = inputProps.value;
|
|
@@ -454,7 +457,9 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
454
457
|
} else if (loadingState !== 'filtering') {
|
|
455
458
|
// If loading is no longer happening, clear any timers and hide the loading circle
|
|
456
459
|
setShowLoading(false);
|
|
457
|
-
|
|
460
|
+
if (timeout.current) {
|
|
461
|
+
clearTimeout(timeout.current);
|
|
462
|
+
}
|
|
458
463
|
timeout.current = null;
|
|
459
464
|
}
|
|
460
465
|
|
|
@@ -464,9 +469,9 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
464
469
|
let onKeyDown = (e) => {
|
|
465
470
|
// Close virtual keyboard if user hits Enter w/o any focused options
|
|
466
471
|
if (e.key === 'Enter' && state.selectionManager.focusedKey == null) {
|
|
467
|
-
popoverRef.current
|
|
472
|
+
popoverRef.current?.focus();
|
|
468
473
|
} else {
|
|
469
|
-
inputProps.onKeyDown(e);
|
|
474
|
+
inputProps.onKeyDown?.(e);
|
|
470
475
|
}
|
|
471
476
|
};
|
|
472
477
|
|
|
@@ -489,11 +494,11 @@ function ComboBoxTray(props: ComboBoxTrayProps) {
|
|
|
489
494
|
inputRef={inputRef}
|
|
490
495
|
isDisabled={isDisabled}
|
|
491
496
|
isLoading={showLoading && loadingState === 'filtering'}
|
|
492
|
-
loadingIndicator={loadingState != null
|
|
497
|
+
loadingIndicator={loadingState != null ? loadingCircle : undefined}
|
|
493
498
|
validationState={validationState}
|
|
494
499
|
labelAlign="start"
|
|
495
500
|
labelPosition="top"
|
|
496
|
-
wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly
|
|
501
|
+
wrapperChildren={(state.inputValue !== '' || loadingState === 'filtering' || validationState != null) && !props.isReadOnly ? clearButton : undefined}
|
|
497
502
|
UNSAFE_className={
|
|
498
503
|
classNames(
|
|
499
504
|
searchStyles,
|