@react-stately/combobox 3.0.0-beta.1 → 3.0.0-nightly-4980928d3-240906
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 +17 -0
- package/dist/main.js +15 -323
- package/dist/main.js.map +1 -1
- package/dist/module.js +16 -307
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +9 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/useComboBoxState.main.js +305 -0
- package/dist/useComboBoxState.main.js.map +1 -0
- package/dist/useComboBoxState.mjs +300 -0
- package/dist/useComboBoxState.module.js +300 -0
- package/dist/useComboBoxState.module.js.map +1 -0
- package/package.json +18 -11
- package/src/index.ts +3 -1
- package/src/useComboBoxState.ts +165 -113
- package/LICENSE +0 -201
package/src/useComboBoxState.ts
CHANGED
|
@@ -10,29 +10,36 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {Collection, FocusStrategy, Node} from '@react-types/shared';
|
|
13
|
+
import {Collection, CollectionStateBase, FocusStrategy, Node} from '@react-types/shared';
|
|
14
14
|
import {ComboBoxProps, MenuTriggerAction} from '@react-types/combobox';
|
|
15
|
+
import {FormValidationState, useFormValidationState} from '@react-stately/form';
|
|
16
|
+
import {getChildNodes} from '@react-stately/collections';
|
|
15
17
|
import {ListCollection, useSingleSelectListState} from '@react-stately/list';
|
|
16
18
|
import {SelectState} from '@react-stately/select';
|
|
19
|
+
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
17
20
|
import {useControlledState} from '@react-stately/utils';
|
|
18
|
-
import {
|
|
19
|
-
import {useMenuTriggerState} from '@react-stately/menu';
|
|
21
|
+
import {useOverlayTriggerState} from '@react-stately/overlays';
|
|
20
22
|
|
|
21
|
-
export interface ComboBoxState<T> extends SelectState<T
|
|
23
|
+
export interface ComboBoxState<T> extends SelectState<T>, FormValidationState{
|
|
22
24
|
/** The current value of the combo box input. */
|
|
23
25
|
inputValue: string,
|
|
24
26
|
/** Sets the value of the combo box input. */
|
|
25
27
|
setInputValue(value: string): void,
|
|
26
28
|
/** Selects the currently focused item and updates the input value. */
|
|
27
29
|
commit(): void,
|
|
30
|
+
/** Controls which item will be auto focused when the menu opens. */
|
|
31
|
+
readonly focusStrategy: FocusStrategy | null,
|
|
28
32
|
/** Opens the menu. */
|
|
29
33
|
open(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void,
|
|
30
34
|
/** Toggles the menu. */
|
|
31
|
-
toggle(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void
|
|
35
|
+
toggle(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void,
|
|
36
|
+
/** Resets the input value to the previously selected item's text if any and closes the menu. */
|
|
37
|
+
revert(): void
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
type FilterFn = (textValue: string, inputValue: string) => boolean;
|
|
35
|
-
|
|
41
|
+
|
|
42
|
+
export interface ComboBoxStateOptions<T> extends Omit<ComboBoxProps<T>, 'children'>, CollectionStateBase<T> {
|
|
36
43
|
/** The filter function used to determine if a option should be included in the combo box list. */
|
|
37
44
|
defaultFilter?: FilterFn,
|
|
38
45
|
/** Whether the combo box allows the menu to be open when the collection is empty. */
|
|
@@ -46,7 +53,7 @@ interface ComboBoxStateProps<T> extends ComboBoxProps<T> {
|
|
|
46
53
|
* of items from props and manages the option selection state of the combo box. In addition, it tracks the input value,
|
|
47
54
|
* focus state, and other properties of the combo box.
|
|
48
55
|
*/
|
|
49
|
-
export function useComboBoxState<T extends object>(props:
|
|
56
|
+
export function useComboBoxState<T extends object>(props: ComboBoxStateOptions<T>): ComboBoxState<T> {
|
|
50
57
|
let {
|
|
51
58
|
defaultFilter,
|
|
52
59
|
menuTrigger = 'input',
|
|
@@ -57,32 +64,46 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
57
64
|
|
|
58
65
|
let [showAllItems, setShowAllItems] = useState(false);
|
|
59
66
|
let [isFocused, setFocusedState] = useState(false);
|
|
60
|
-
let [
|
|
61
|
-
props.inputValue,
|
|
62
|
-
props.defaultInputValue ?? '',
|
|
63
|
-
props.onInputChange
|
|
64
|
-
);
|
|
67
|
+
let [focusStrategy, setFocusStrategy] = useState<FocusStrategy | null>(null);
|
|
65
68
|
|
|
66
69
|
let onSelectionChange = (key) => {
|
|
67
70
|
if (props.onSelectionChange) {
|
|
68
71
|
props.onSelectionChange(key);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
// If
|
|
74
|
+
// If key is the same, reset the inputValue and close the menu
|
|
72
75
|
// (scenario: user clicks on already selected option)
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
triggerState.close();
|
|
77
|
-
}
|
|
76
|
+
if (key === selectedKey) {
|
|
77
|
+
resetInputValue();
|
|
78
|
+
closeMenu();
|
|
78
79
|
}
|
|
79
80
|
};
|
|
80
81
|
|
|
81
|
-
let {collection,
|
|
82
|
+
let {collection,
|
|
83
|
+
selectionManager,
|
|
84
|
+
selectedKey,
|
|
85
|
+
setSelectedKey,
|
|
86
|
+
selectedItem,
|
|
87
|
+
disabledKeys
|
|
88
|
+
} = useSingleSelectListState({
|
|
82
89
|
...props,
|
|
83
90
|
onSelectionChange,
|
|
84
91
|
items: props.items ?? props.defaultItems
|
|
85
92
|
});
|
|
93
|
+
let defaultInputValue: string | null | undefined = props.defaultInputValue;
|
|
94
|
+
if (defaultInputValue == null) {
|
|
95
|
+
if (selectedKey == null) {
|
|
96
|
+
defaultInputValue = '';
|
|
97
|
+
} else {
|
|
98
|
+
defaultInputValue = collection.getItem(selectedKey)?.textValue ?? '';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let [inputValue, setInputValue] = useControlledState(
|
|
103
|
+
props.inputValue,
|
|
104
|
+
defaultInputValue!,
|
|
105
|
+
props.onInputChange
|
|
106
|
+
);
|
|
86
107
|
|
|
87
108
|
// Preserve original collection so we can show all items on demand
|
|
88
109
|
let originalCollection = collection;
|
|
@@ -92,17 +113,23 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
92
113
|
? collection
|
|
93
114
|
: filterCollection(collection, inputValue, defaultFilter)
|
|
94
115
|
), [collection, inputValue, defaultFilter, props.items]);
|
|
116
|
+
let [lastCollection, setLastCollection] = useState(filteredCollection);
|
|
95
117
|
|
|
96
118
|
// Track what action is attempting to open the menu
|
|
97
|
-
let menuOpenTrigger = useRef('focus'
|
|
119
|
+
let menuOpenTrigger = useRef<MenuTriggerAction | undefined>('focus');
|
|
98
120
|
let onOpenChange = (open: boolean) => {
|
|
99
121
|
if (props.onOpenChange) {
|
|
100
122
|
props.onOpenChange(open, open ? menuOpenTrigger.current : undefined);
|
|
101
123
|
}
|
|
124
|
+
|
|
125
|
+
selectionManager.setFocused(open);
|
|
126
|
+
if (!open) {
|
|
127
|
+
selectionManager.setFocusedKey(null);
|
|
128
|
+
}
|
|
102
129
|
};
|
|
103
130
|
|
|
104
|
-
let triggerState =
|
|
105
|
-
let open = (focusStrategy
|
|
131
|
+
let triggerState = useOverlayTriggerState({...props, onOpenChange, isOpen: undefined, defaultOpen: undefined});
|
|
132
|
+
let open = (focusStrategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
|
|
106
133
|
let displayAllItems = (trigger === 'manual' || (trigger === 'focus' && menuTrigger === 'focus'));
|
|
107
134
|
// Prevent open operations from triggering if there is nothing to display
|
|
108
135
|
// Also prevent open operations from triggering if items are uncontrolled but defaultItems is empty, even if displayAllItems is true.
|
|
@@ -114,11 +141,12 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
114
141
|
}
|
|
115
142
|
|
|
116
143
|
menuOpenTrigger.current = trigger;
|
|
117
|
-
|
|
144
|
+
setFocusStrategy(focusStrategy);
|
|
145
|
+
triggerState.open();
|
|
118
146
|
}
|
|
119
147
|
};
|
|
120
148
|
|
|
121
|
-
let toggle = (focusStrategy
|
|
149
|
+
let toggle = (focusStrategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
|
|
122
150
|
let displayAllItems = (trigger === 'manual' || (trigger === 'focus' && menuTrigger === 'focus'));
|
|
123
151
|
// If the menu is closed and there is nothing to display, early return so toggle isn't called to prevent extraneous onOpenChange
|
|
124
152
|
if (!(allowsEmptyCollection || filteredCollection.size > 0 || (displayAllItems && originalCollection.size > 0) || props.items) && !triggerState.isOpen) {
|
|
@@ -135,58 +163,78 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
135
163
|
menuOpenTrigger.current = trigger;
|
|
136
164
|
}
|
|
137
165
|
|
|
138
|
-
|
|
166
|
+
toggleMenu(focusStrategy);
|
|
139
167
|
};
|
|
140
168
|
|
|
141
|
-
let
|
|
169
|
+
let updateLastCollection = useCallback(() => {
|
|
170
|
+
setLastCollection(showAllItems ? originalCollection : filteredCollection);
|
|
171
|
+
}, [showAllItems, originalCollection, filteredCollection]);
|
|
172
|
+
|
|
173
|
+
// If menu is going to close, save the current collection so we can freeze the displayed collection when the
|
|
174
|
+
// user clicks outside the popover to close the menu. Prevents the menu contents from updating as the menu closes.
|
|
175
|
+
let toggleMenu = useCallback((focusStrategy: FocusStrategy | null = null) => {
|
|
176
|
+
if (triggerState.isOpen) {
|
|
177
|
+
updateLastCollection();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
setFocusStrategy(focusStrategy);
|
|
181
|
+
triggerState.toggle();
|
|
182
|
+
}, [triggerState, updateLastCollection]);
|
|
183
|
+
|
|
184
|
+
let closeMenu = useCallback(() => {
|
|
185
|
+
if (triggerState.isOpen) {
|
|
186
|
+
updateLastCollection();
|
|
187
|
+
triggerState.close();
|
|
188
|
+
}
|
|
189
|
+
}, [triggerState, updateLastCollection]);
|
|
190
|
+
|
|
191
|
+
let [lastValue, setLastValue] = useState(inputValue);
|
|
142
192
|
let resetInputValue = () => {
|
|
143
|
-
let itemText = collection.getItem(selectedKey)?.textValue ?? '';
|
|
144
|
-
|
|
193
|
+
let itemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
|
|
194
|
+
setLastValue(itemText);
|
|
145
195
|
setInputValue(itemText);
|
|
146
196
|
};
|
|
147
197
|
|
|
148
|
-
let isInitialRender = useRef(true);
|
|
149
198
|
let lastSelectedKey = useRef(props.selectedKey ?? props.defaultSelectedKey ?? null);
|
|
150
|
-
let lastSelectedKeyText = useRef(
|
|
199
|
+
let lastSelectedKeyText = useRef(
|
|
200
|
+
selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : ''
|
|
201
|
+
);
|
|
151
202
|
// intentional omit dependency array, want this to happen on every render
|
|
152
203
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
153
204
|
useEffect(() => {
|
|
154
|
-
//
|
|
155
|
-
//
|
|
205
|
+
// Open and close menu automatically when the input value changes if the input is focused,
|
|
206
|
+
// and there are items in the collection or allowEmptyCollection is true.
|
|
156
207
|
if (
|
|
157
208
|
isFocused &&
|
|
158
209
|
(filteredCollection.size > 0 || allowsEmptyCollection) &&
|
|
159
210
|
!triggerState.isOpen &&
|
|
160
|
-
inputValue !== lastValue
|
|
161
|
-
menuTrigger !== 'manual'
|
|
162
|
-
(props.isOpen === undefined || props.inputValue === undefined)
|
|
211
|
+
inputValue !== lastValue &&
|
|
212
|
+
menuTrigger !== 'manual'
|
|
163
213
|
) {
|
|
164
214
|
open(null, 'input');
|
|
165
215
|
}
|
|
166
216
|
|
|
167
|
-
// Close the menu if the collection is empty
|
|
168
|
-
//
|
|
217
|
+
// Close the menu if the collection is empty. Don't close menu if filtered collection size is 0
|
|
218
|
+
// but we are currently showing all items via button press
|
|
169
219
|
if (
|
|
170
220
|
!showAllItems &&
|
|
171
221
|
!allowsEmptyCollection &&
|
|
172
222
|
triggerState.isOpen &&
|
|
173
|
-
filteredCollection.size === 0
|
|
174
|
-
(props.isOpen === undefined || props.items === undefined)
|
|
223
|
+
filteredCollection.size === 0
|
|
175
224
|
) {
|
|
176
|
-
|
|
225
|
+
closeMenu();
|
|
177
226
|
}
|
|
178
227
|
|
|
179
|
-
// Close when an item is selected
|
|
228
|
+
// Close when an item is selected.
|
|
180
229
|
if (
|
|
181
230
|
selectedKey != null &&
|
|
182
|
-
selectedKey !== lastSelectedKey.current
|
|
183
|
-
(props.isOpen === undefined || props.selectedKey === undefined)
|
|
231
|
+
selectedKey !== lastSelectedKey.current
|
|
184
232
|
) {
|
|
185
|
-
|
|
233
|
+
closeMenu();
|
|
186
234
|
}
|
|
187
235
|
|
|
188
236
|
// Clear focused key when input value changes and display filtered collection again.
|
|
189
|
-
if (inputValue !== lastValue
|
|
237
|
+
if (inputValue !== lastValue) {
|
|
190
238
|
selectionManager.setFocusedKey(null);
|
|
191
239
|
setShowAllItems(false);
|
|
192
240
|
|
|
@@ -197,11 +245,6 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
197
245
|
}
|
|
198
246
|
}
|
|
199
247
|
|
|
200
|
-
// If it is the intial render and inputValue isn't controlled nor has an intial value, set input to match current selected key if any
|
|
201
|
-
if (isInitialRender.current && (props.inputValue === undefined && props.defaultInputValue === undefined)) {
|
|
202
|
-
resetInputValue();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
248
|
// If the selectedKey changed, update the input value.
|
|
206
249
|
// Do nothing if both inputValue and selectedKey are controlled.
|
|
207
250
|
// In this case, it's the user's responsibility to update inputValue in onSelectionChange.
|
|
@@ -210,81 +253,69 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
210
253
|
(props.inputValue === undefined || props.selectedKey === undefined)
|
|
211
254
|
) {
|
|
212
255
|
resetInputValue();
|
|
213
|
-
} else {
|
|
214
|
-
|
|
256
|
+
} else if (lastValue !== inputValue) {
|
|
257
|
+
setLastValue(inputValue);
|
|
215
258
|
}
|
|
216
259
|
|
|
217
260
|
// Update the inputValue if the selected item's text changes from its last tracked value.
|
|
218
261
|
// This is to handle cases where a selectedKey is specified but the items aren't available (async loading) or the selected item's text value updates.
|
|
219
262
|
// Only reset if the user isn't currently within the field so we don't erroneously modify user input.
|
|
220
263
|
// If inputValue is controlled, it is the user's responsibility to update the inputValue when items change.
|
|
221
|
-
let selectedItemText = collection.getItem(selectedKey)?.textValue ?? '';
|
|
264
|
+
let selectedItemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
|
|
222
265
|
if (!isFocused && selectedKey != null && props.inputValue === undefined && selectedKey === lastSelectedKey.current) {
|
|
223
266
|
if (lastSelectedKeyText.current !== selectedItemText) {
|
|
224
|
-
|
|
267
|
+
setLastValue(selectedItemText);
|
|
225
268
|
setInputValue(selectedItemText);
|
|
226
269
|
}
|
|
227
270
|
}
|
|
228
271
|
|
|
229
|
-
isInitialRender.current = false;
|
|
230
272
|
lastSelectedKey.current = selectedKey;
|
|
231
273
|
lastSelectedKeyText.current = selectedItemText;
|
|
232
274
|
});
|
|
233
275
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
276
|
+
let validation = useFormValidationState({
|
|
277
|
+
...props,
|
|
278
|
+
value: useMemo(() => ({inputValue, selectedKey}), [inputValue, selectedKey])
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Revert input value and close menu
|
|
282
|
+
let revert = () => {
|
|
283
|
+
if (allowsCustomValue && selectedKey == null) {
|
|
284
|
+
commitCustomValue();
|
|
285
|
+
} else {
|
|
286
|
+
commitSelection();
|
|
238
287
|
}
|
|
239
|
-
}
|
|
288
|
+
};
|
|
240
289
|
|
|
241
290
|
let commitCustomValue = () => {
|
|
242
|
-
let shouldClose = false;
|
|
243
291
|
lastSelectedKey.current = null;
|
|
244
292
|
setSelectedKey(null);
|
|
245
|
-
|
|
246
|
-
// If previous key was already null, need to manually call onSelectionChange since it won't be triggered by a setSelectedKey call
|
|
247
|
-
// This allows the application to control whether or not to close the menu on custom value commit
|
|
248
|
-
if (selectedKey === null && props.onSelectionChange) {
|
|
249
|
-
props.onSelectionChange(null);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Should close menu ourselves if component open state or selected key is uncontrolled and therefore won't be closed by a user defined event handler
|
|
253
|
-
shouldClose = props.isOpen == null || props.selectedKey === undefined;
|
|
254
|
-
|
|
255
|
-
// Close if no other event will be fired. Otherwise, allow the
|
|
256
|
-
// application to control this based on that event.
|
|
257
|
-
if (shouldClose) {
|
|
258
|
-
triggerState.close();
|
|
259
|
-
}
|
|
293
|
+
closeMenu();
|
|
260
294
|
};
|
|
261
295
|
|
|
262
296
|
let commitSelection = () => {
|
|
263
297
|
// If multiple things are controlled, call onSelectionChange
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
(props.selectedKey !== undefined && props.inputValue !== undefined) ||
|
|
267
|
-
(props.inputValue !== undefined && props.isOpen !== undefined) ||
|
|
268
|
-
(props.selectedKey !== undefined && props.isOpen !== undefined)
|
|
269
|
-
) {
|
|
270
|
-
props.onSelectionChange(selectedKey);
|
|
271
|
-
|
|
272
|
-
// If multiple things are controlled but inputValue isn't, reset the input value for the user
|
|
273
|
-
if (props.inputValue === undefined) {
|
|
274
|
-
resetInputValue();
|
|
275
|
-
}
|
|
298
|
+
if (props.selectedKey !== undefined && props.inputValue !== undefined) {
|
|
299
|
+
props.onSelectionChange?.(selectedKey);
|
|
276
300
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
lastValue.current = itemText;
|
|
282
|
-
triggerState.close();
|
|
283
|
-
}
|
|
301
|
+
// Stop menu from reopening from useEffect
|
|
302
|
+
let itemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
|
|
303
|
+
setLastValue(itemText);
|
|
304
|
+
closeMenu();
|
|
284
305
|
} else {
|
|
285
306
|
// If only a single aspect of combobox is controlled, reset input value and close menu for the user
|
|
286
307
|
resetInputValue();
|
|
287
|
-
|
|
308
|
+
closeMenu();
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const commitValue = () => {
|
|
313
|
+
if (allowsCustomValue) {
|
|
314
|
+
const itemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
|
|
315
|
+
(inputValue === itemText) ? commitSelection() : commitCustomValue();
|
|
316
|
+
} else {
|
|
317
|
+
// Reset inputValue and close menu
|
|
318
|
+
commitSelection();
|
|
288
319
|
}
|
|
289
320
|
};
|
|
290
321
|
|
|
@@ -297,32 +328,50 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
297
328
|
} else {
|
|
298
329
|
setSelectedKey(selectionManager.focusedKey);
|
|
299
330
|
}
|
|
300
|
-
} else
|
|
301
|
-
|
|
331
|
+
} else {
|
|
332
|
+
commitValue();
|
|
302
333
|
}
|
|
303
334
|
};
|
|
304
335
|
|
|
336
|
+
let valueOnFocus = useRef(inputValue);
|
|
305
337
|
let setFocused = (isFocused: boolean) => {
|
|
306
338
|
if (isFocused) {
|
|
307
|
-
|
|
339
|
+
valueOnFocus.current = inputValue;
|
|
340
|
+
if (menuTrigger === 'focus' && !props.isReadOnly) {
|
|
308
341
|
open(null, 'focus');
|
|
309
342
|
}
|
|
310
|
-
} else
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
343
|
+
} else {
|
|
344
|
+
if (shouldCloseOnBlur) {
|
|
345
|
+
commitValue();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (inputValue !== valueOnFocus.current) {
|
|
349
|
+
validation.commitValidation();
|
|
316
350
|
}
|
|
317
351
|
}
|
|
318
352
|
|
|
319
353
|
setFocusedState(isFocused);
|
|
320
354
|
};
|
|
321
355
|
|
|
356
|
+
let displayedCollection = useMemo(() => {
|
|
357
|
+
if (triggerState.isOpen) {
|
|
358
|
+
if (showAllItems) {
|
|
359
|
+
return originalCollection;
|
|
360
|
+
} else {
|
|
361
|
+
return filteredCollection;
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
return lastCollection;
|
|
365
|
+
}
|
|
366
|
+
}, [triggerState.isOpen, originalCollection, filteredCollection, showAllItems, lastCollection]);
|
|
367
|
+
|
|
322
368
|
return {
|
|
369
|
+
...validation,
|
|
323
370
|
...triggerState,
|
|
371
|
+
focusStrategy,
|
|
324
372
|
toggle,
|
|
325
373
|
open,
|
|
374
|
+
close: commitValue,
|
|
326
375
|
selectionManager,
|
|
327
376
|
selectedKey,
|
|
328
377
|
setSelectedKey,
|
|
@@ -330,26 +379,29 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>)
|
|
|
330
379
|
isFocused,
|
|
331
380
|
setFocused,
|
|
332
381
|
selectedItem,
|
|
333
|
-
collection:
|
|
382
|
+
collection: displayedCollection,
|
|
334
383
|
inputValue,
|
|
335
384
|
setInputValue,
|
|
336
|
-
commit
|
|
385
|
+
commit,
|
|
386
|
+
revert
|
|
337
387
|
};
|
|
338
388
|
}
|
|
339
389
|
|
|
340
390
|
function filterCollection<T extends object>(collection: Collection<Node<T>>, inputValue: string, filter: FilterFn): Collection<Node<T>> {
|
|
341
|
-
return new ListCollection(filterNodes(collection, inputValue, filter));
|
|
391
|
+
return new ListCollection(filterNodes(collection, collection, inputValue, filter));
|
|
342
392
|
}
|
|
343
393
|
|
|
344
|
-
function filterNodes<T>(nodes: Iterable<Node<T>>, inputValue: string, filter: FilterFn): Iterable<Node<T>> {
|
|
345
|
-
let filteredNode = [];
|
|
394
|
+
function filterNodes<T>(collection: Collection<Node<T>>, nodes: Iterable<Node<T>>, inputValue: string, filter: FilterFn): Iterable<Node<T>> {
|
|
395
|
+
let filteredNode: Node<T>[] = [];
|
|
346
396
|
for (let node of nodes) {
|
|
347
397
|
if (node.type === 'section' && node.hasChildNodes) {
|
|
348
|
-
let filtered = filterNodes(node
|
|
349
|
-
if ([...filtered].
|
|
398
|
+
let filtered = filterNodes(collection, getChildNodes(node, collection), inputValue, filter);
|
|
399
|
+
if ([...filtered].some(node => node.type === 'item')) {
|
|
350
400
|
filteredNode.push({...node, childNodes: filtered});
|
|
351
401
|
}
|
|
352
|
-
} else if (node.type
|
|
402
|
+
} else if (node.type === 'item' && filter(node.textValue, inputValue)) {
|
|
403
|
+
filteredNode.push({...node});
|
|
404
|
+
} else if (node.type !== 'item') {
|
|
353
405
|
filteredNode.push({...node});
|
|
354
406
|
}
|
|
355
407
|
}
|