@sproutsocial/seeds-react-menu 1.16.16 → 1.18.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/.turbo/turbo-build.log +14 -22
- package/CHANGELOG.md +51 -0
- package/dist/esm/index.js +542 -229
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v3/index.js +870 -1081
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/v3/index.d.mts +82 -38
- package/dist/v3/index.d.ts +82 -38
- package/dist/v3/index.js +887 -1098
- package/dist/v3/index.js.map +1 -1
- package/dist/v3/menu.css +947 -0
- package/package.json +20 -21
- package/src/ActionMenu/ActionMenu.stories.tsx +1 -1
- package/src/Autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/MenuContent/MenuContent.stories.tsx +1 -1
- package/src/MenuFooter/MenuFooter.stories.tsx +1 -1
- package/src/MenuGroup/MenuGroup.stories.tsx +1 -1
- package/src/MenuHeader/MenuHeader.stories.tsx +1 -1
- package/src/MenuItem/MenuItem.stories.tsx +1 -1
- package/src/MenuToggleButton/MenuToggleButton.stories.tsx +1 -1
- package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +1 -1
- package/src/NestedMenu/NestedMenu.stories.tsx +1 -1
- package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +1 -1
- package/src/SubmenuItem/SubmenuItem.stories.tsx +1 -1
- package/src/v3/ActionMenu.stories.tsx +1 -1
- package/src/v3/ActionMenuStyles.tsx +169 -149
- package/src/v3/Common/ComboboxStyles.tsx +396 -511
- package/src/v3/Common/SelectIcons.tsx +17 -8
- package/src/v3/Common/SelectItem.tsx +25 -38
- package/src/v3/Common/SelectStyles.tsx +146 -144
- package/src/v3/Common/cn.ts +38 -0
- package/src/v3/MenuButton.stories.tsx +1 -1
- package/src/v3/MenuButtonStyles.tsx +55 -34
- package/src/v3/ModalStories.stories.tsx +1 -1
- package/src/v3/MultiCombobox.stories.tsx +1 -1
- package/src/v3/MultiCombobox.tsx +7 -0
- package/src/v3/MultiSearchableSelect.stories.tsx +1 -1
- package/src/v3/MultiSearchableSelect.tsx +15 -8
- package/src/v3/MultiSelect.stories.tsx +1 -1
- package/src/v3/MultiSelect.tsx +20 -17
- package/src/v3/SingleCombobox.stories.tsx +1 -1
- package/src/v3/SingleCombobox.tsx +7 -0
- package/src/v3/SingleSearchableSelect.stories.tsx +1 -1
- package/src/v3/SingleSearchableSelect.tsx +7 -0
- package/src/v3/SingleSelect.stories.tsx +1 -1
- package/src/v3/SingleSelect.tsx +22 -14
- package/src/v3/__tests__/ariaLabels.test.tsx +133 -0
- package/src/v3/menu.css +947 -0
- package/tsup.config.ts +0 -1
- package/dist/esm/chunk-ROQATIB7.js +0 -357
- package/dist/esm/chunk-ROQATIB7.js.map +0 -1
- package/dist/esm/v2/index.js +0 -2089
- package/dist/esm/v2/index.js.map +0 -1
- package/dist/v2/index.d.mts +0 -108
- package/dist/v2/index.d.ts +0 -108
- package/dist/v2/index.js +0 -2280
- package/dist/v2/index.js.map +0 -1
- package/src/v2/Autocomplete/Autocomplete.stories.tsx +0 -645
- package/src/v2/Autocomplete/MultiAutocomplete.tsx +0 -487
- package/src/v2/Autocomplete/SingleAutocomplete.tsx +0 -366
- package/src/v2/Autocomplete/index.ts +0 -2
- package/src/v2/Common-V2/ComboboxContent.tsx +0 -457
- package/src/v2/Common-V2/MenuGroup.tsx +0 -60
- package/src/v2/Common-V2/MenuGroupTypes.ts +0 -30
- package/src/v2/Common-V2/MenuHeading.tsx +0 -83
- package/src/v2/Common-V2/MenuItem.tsx +0 -138
- package/src/v2/Common-V2/Popout.tsx +0 -124
- package/src/v2/Common-V2/PopoutTypes.tsx +0 -109
- package/src/v2/Common-V2/SelectToggleButton.tsx +0 -99
- package/src/v2/Common-V2/index.ts +0 -11
- package/src/v2/Common-V2/props.ts +0 -84
- package/src/v2/Common-V2/styles.tsx +0 -41
- package/src/v2/Common-V2/types.ts +0 -16
- package/src/v2/Common-V2/useHighlightedIndex.ts +0 -62
- package/src/v2/Common-V2/utils.ts +0 -238
- package/src/v2/SearchableSelect/AutocompleteContent.tsx +0 -325
- package/src/v2/SearchableSelect/SearchableMultiSelect.tsx +0 -527
- package/src/v2/SearchableSelect/SearchableSelect.tsx +0 -395
- package/src/v2/SearchableSelect/index.ts +0 -12
- package/src/v2/Select/MultiSelect.tsx +0 -417
- package/src/v2/Select/Select.stories.tsx +0 -593
- package/src/v2/Select/SingleSelect.tsx +0 -285
- package/src/v2/Select/index.ts +0 -2
- package/src/v2/index.ts +0 -2
|
@@ -1,487 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo, useRef } from "react";
|
|
2
|
-
import { useCombobox, useMultipleSelection } from "downshift";
|
|
3
|
-
import { TokenInput } from "@sproutsocial/seeds-react-token-input";
|
|
4
|
-
// This will eventually be imported from seeds-react-popout
|
|
5
|
-
import { Popout } from "../Common-V2";
|
|
6
|
-
import {
|
|
7
|
-
isGroupHeading,
|
|
8
|
-
isToggleItem,
|
|
9
|
-
menuAnimationConfig,
|
|
10
|
-
getFilteredItems,
|
|
11
|
-
groupItemsWithHeadings,
|
|
12
|
-
useHighlightedIndex,
|
|
13
|
-
createItemToStringForDownshift,
|
|
14
|
-
ComboboxContent,
|
|
15
|
-
} from "../Common-V2";
|
|
16
|
-
import {
|
|
17
|
-
type DataWithId,
|
|
18
|
-
type GroupedItem,
|
|
19
|
-
type ToggleItem,
|
|
20
|
-
type GroupHeading,
|
|
21
|
-
type BaseMultiAutocompleteProps,
|
|
22
|
-
} from "../Common-V2";
|
|
23
|
-
|
|
24
|
-
function defaultReducer(state, actionAndChanges) {
|
|
25
|
-
const { changes, type } = actionAndChanges;
|
|
26
|
-
|
|
27
|
-
switch (type) {
|
|
28
|
-
case useCombobox.stateChangeTypes.InputBlur:
|
|
29
|
-
// Do not close the menu on blur from this event
|
|
30
|
-
// There is a very brief blur when clicking an item, even though the input seemingly does not lose focus
|
|
31
|
-
// Closing the menu is handled in the onBlur handler instead, which does not have this issue
|
|
32
|
-
return {
|
|
33
|
-
...changes,
|
|
34
|
-
isOpen: true,
|
|
35
|
-
};
|
|
36
|
-
case useCombobox.stateChangeTypes.InputKeyDownEnter:
|
|
37
|
-
case useCombobox.stateChangeTypes.ItemClick:
|
|
38
|
-
return {
|
|
39
|
-
...changes,
|
|
40
|
-
isOpen: true, // keep the menu open after selection.
|
|
41
|
-
highlightedIndex: state.highlightedIndex, // preserve the current position so the list doesn't jump.
|
|
42
|
-
};
|
|
43
|
-
default:
|
|
44
|
-
return changes;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface MultiAutocompleteProps<T extends DataWithId>
|
|
49
|
-
extends BaseMultiAutocompleteProps<T> {}
|
|
50
|
-
|
|
51
|
-
export function MultiAutocomplete<T extends DataWithId>({
|
|
52
|
-
id,
|
|
53
|
-
data,
|
|
54
|
-
selectedItemIds: controlledSelectedItemIds,
|
|
55
|
-
inputValue: controlledInputValue,
|
|
56
|
-
isOpen: controlledIsOpen,
|
|
57
|
-
itemToKey = (item: T) => item?.id,
|
|
58
|
-
itemToString,
|
|
59
|
-
itemToSearchString,
|
|
60
|
-
renderItem,
|
|
61
|
-
groupBy,
|
|
62
|
-
renderGroupHeading,
|
|
63
|
-
isVirtualized = false,
|
|
64
|
-
estimatedItemHeight,
|
|
65
|
-
showSelectedItems = false,
|
|
66
|
-
selectHeadings = false,
|
|
67
|
-
selectAllItem,
|
|
68
|
-
EmptyState,
|
|
69
|
-
placeholder,
|
|
70
|
-
onSelectedItemIdsChange,
|
|
71
|
-
onInputValueChange,
|
|
72
|
-
onIsOpenChange,
|
|
73
|
-
stateReducer = defaultReducer,
|
|
74
|
-
menuProps,
|
|
75
|
-
inputType = "checkbox",
|
|
76
|
-
onPointerDownOutside,
|
|
77
|
-
onInteractOutside,
|
|
78
|
-
popoutPlacement,
|
|
79
|
-
}: MultiAutocompleteProps<T>) {
|
|
80
|
-
const mousedownInPopoverRef = useRef(false);
|
|
81
|
-
|
|
82
|
-
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
83
|
-
useState<string>("");
|
|
84
|
-
const [uncontrolledSelectedItemIds, setUncontrolledSelectedItemIds] =
|
|
85
|
-
useState<T["id"][]>([]);
|
|
86
|
-
const [uncontrolledIsOpen, setUncontrolledIsOpen] = useState<boolean>(false);
|
|
87
|
-
|
|
88
|
-
// Use controlled selectedItemIds if provided, otherwise use internal state
|
|
89
|
-
const isSelectedItemIdsControlled = controlledSelectedItemIds !== undefined;
|
|
90
|
-
const selectedItemIds = isSelectedItemIdsControlled
|
|
91
|
-
? controlledSelectedItemIds
|
|
92
|
-
: uncontrolledSelectedItemIds;
|
|
93
|
-
|
|
94
|
-
// Convert selectedItemIds to selectedItems for internal use
|
|
95
|
-
const selectedItems = useMemo<T[]>(() => {
|
|
96
|
-
return data.filter((item) => selectedItemIds.includes(itemToKey(item)));
|
|
97
|
-
}, [selectedItemIds, data, itemToKey]);
|
|
98
|
-
|
|
99
|
-
// Use controlled inputValue if provided, otherwise use internal state
|
|
100
|
-
const isInputValueControlled = controlledInputValue !== undefined;
|
|
101
|
-
const inputValue = isInputValueControlled
|
|
102
|
-
? controlledInputValue
|
|
103
|
-
: uncontrolledInputValue;
|
|
104
|
-
|
|
105
|
-
// Use controlled isOpen if provided, otherwise use internal state
|
|
106
|
-
const isIsOpenControlled = controlledIsOpen !== undefined;
|
|
107
|
-
const isOpen = isIsOpenControlled ? controlledIsOpen : uncontrolledIsOpen;
|
|
108
|
-
|
|
109
|
-
const updateSelectedItems = (newSelectedItems: T[]) => {
|
|
110
|
-
const newSelectedItemIds = newSelectedItems.map((item) => itemToKey(item));
|
|
111
|
-
if (!isSelectedItemIdsControlled) {
|
|
112
|
-
setUncontrolledSelectedItemIds(newSelectedItemIds);
|
|
113
|
-
}
|
|
114
|
-
onSelectedItemIdsChange?.(newSelectedItemIds);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const updateInputValue = (newInputValue: string) => {
|
|
118
|
-
if (!isInputValueControlled) {
|
|
119
|
-
setUncontrolledInputValue(newInputValue);
|
|
120
|
-
}
|
|
121
|
-
onInputValueChange?.(newInputValue);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const updateIsOpen = (newIsOpen: boolean) => {
|
|
125
|
-
if (!newIsOpen) {
|
|
126
|
-
updateInputValue("");
|
|
127
|
-
}
|
|
128
|
-
if (!isIsOpenControlled) {
|
|
129
|
-
setUncontrolledIsOpen(newIsOpen);
|
|
130
|
-
}
|
|
131
|
-
onIsOpenChange?.(newIsOpen);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Use itemToSearchString if provided, otherwise fallback to itemToString
|
|
135
|
-
const searchStringFn =
|
|
136
|
-
itemToSearchString ?? ((item: T) => itemToString(item));
|
|
137
|
-
|
|
138
|
-
const filteredItems = useMemo<T[]>(
|
|
139
|
-
() =>
|
|
140
|
-
getFilteredItems(data, inputValue, searchStringFn, itemToString, {
|
|
141
|
-
selectedItems,
|
|
142
|
-
showSelectedItems,
|
|
143
|
-
}),
|
|
144
|
-
[data, selectedItems, inputValue, searchStringFn, showSelectedItems]
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
const items = useMemo<GroupedItem<T>[]>(() => {
|
|
148
|
-
let listItems: GroupedItem<T>[];
|
|
149
|
-
|
|
150
|
-
// If groupBy is provided and selectHeadings is true we need to add group heading items to the list
|
|
151
|
-
if (groupBy) {
|
|
152
|
-
// Group first, then add selectAllItem at the beginning
|
|
153
|
-
// If headings are not selectable we will just reorder the items
|
|
154
|
-
// If headings are selectable we will add them to the list
|
|
155
|
-
listItems = groupItemsWithHeadings(
|
|
156
|
-
filteredItems,
|
|
157
|
-
groupBy,
|
|
158
|
-
data,
|
|
159
|
-
selectHeadings
|
|
160
|
-
);
|
|
161
|
-
} else {
|
|
162
|
-
listItems = filteredItems;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Add selectAllItem at the beginning if provided
|
|
166
|
-
if (selectAllItem) {
|
|
167
|
-
return [selectAllItem, ...listItems];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return listItems;
|
|
171
|
-
}, [filteredItems, groupBy, selectAllItem, selectHeadings, data]);
|
|
172
|
-
|
|
173
|
-
// Handler for heading clicks - selects/deselects all items in a group
|
|
174
|
-
const handleHeadingClick = useMemo(() => {
|
|
175
|
-
if (!selectHeadings || !groupBy) {
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return (groupLabel: string, allSelected: boolean) => {
|
|
180
|
-
// Find all items in this group from the full data array
|
|
181
|
-
const groupItems = data.filter((item) => groupBy(item) === groupLabel);
|
|
182
|
-
|
|
183
|
-
if (allSelected) {
|
|
184
|
-
// Deselect all items in the group
|
|
185
|
-
const newSelectedItems = selectedItems.filter(
|
|
186
|
-
(item) => !groupItems.includes(item)
|
|
187
|
-
);
|
|
188
|
-
updateSelectedItems(newSelectedItems);
|
|
189
|
-
} else {
|
|
190
|
-
// Select all items in the group (add them if not already selected)
|
|
191
|
-
const newSelectedItems = [...selectedItems];
|
|
192
|
-
for (const groupItem of groupItems) {
|
|
193
|
-
if (!newSelectedItems.includes(groupItem)) {
|
|
194
|
-
newSelectedItems.push(groupItem);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
updateSelectedItems(newSelectedItems);
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
}, [selectHeadings, groupBy, data, selectedItems, updateSelectedItems]);
|
|
201
|
-
|
|
202
|
-
const { getSelectedItemProps, getDropdownProps, removeSelectedItem } =
|
|
203
|
-
useMultipleSelection<T>({
|
|
204
|
-
selectedItems,
|
|
205
|
-
onStateChange({ selectedItems: newSelectedItems, type }) {
|
|
206
|
-
switch (type) {
|
|
207
|
-
case useMultipleSelection.stateChangeTypes
|
|
208
|
-
.SelectedItemKeyDownBackspace:
|
|
209
|
-
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownDelete:
|
|
210
|
-
case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
|
|
211
|
-
case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
|
|
212
|
-
if (newSelectedItems !== undefined) {
|
|
213
|
-
updateSelectedItems(newSelectedItems);
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
216
|
-
default:
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// Filter out headings for downshift unless selectHeadings is true
|
|
223
|
-
const itemsForCombobox = useMemo<(T | ToggleItem | GroupHeading)[]>(() => {
|
|
224
|
-
if (selectHeadings) {
|
|
225
|
-
// Include all items (headings, toggle items, and data items)
|
|
226
|
-
return items;
|
|
227
|
-
}
|
|
228
|
-
return items.filter(
|
|
229
|
-
(item): item is T | ToggleItem => !isGroupHeading(item)
|
|
230
|
-
);
|
|
231
|
-
}, [items, selectHeadings]);
|
|
232
|
-
|
|
233
|
-
// Wrapper function to handle ToggleItem and GroupHeading separately
|
|
234
|
-
const itemToStringForDownshift = useMemo(
|
|
235
|
-
() => createItemToStringForDownshift(itemToString),
|
|
236
|
-
[itemToString]
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const comboboxProps = useCombobox<T | ToggleItem | GroupHeading>({
|
|
240
|
-
inputId: id,
|
|
241
|
-
items: itemsForCombobox,
|
|
242
|
-
itemToString: itemToStringForDownshift,
|
|
243
|
-
defaultHighlightedIndex: -1,
|
|
244
|
-
selectedItem: null,
|
|
245
|
-
inputValue,
|
|
246
|
-
isOpen,
|
|
247
|
-
stateReducer,
|
|
248
|
-
// Could maybe let devs override this too?
|
|
249
|
-
onStateChange({
|
|
250
|
-
inputValue: newInputValue,
|
|
251
|
-
type,
|
|
252
|
-
selectedItem: newSelectedItem,
|
|
253
|
-
isOpen: newIsOpen,
|
|
254
|
-
}) {
|
|
255
|
-
// Handle isOpen changes for any state change type
|
|
256
|
-
if (newIsOpen !== undefined) {
|
|
257
|
-
updateIsOpen(newIsOpen);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
switch (type) {
|
|
261
|
-
case useCombobox.stateChangeTypes.InputKeyDownEnter:
|
|
262
|
-
case useCombobox.stateChangeTypes.ItemClick:
|
|
263
|
-
if (newSelectedItem) {
|
|
264
|
-
// Handle selectAllItem selection
|
|
265
|
-
if (
|
|
266
|
-
selectAllItem &&
|
|
267
|
-
isToggleItem(newSelectedItem) &&
|
|
268
|
-
newSelectedItem.id === selectAllItem.id
|
|
269
|
-
) {
|
|
270
|
-
// Check if all items are selected (excluding selectAllItem itself)
|
|
271
|
-
const allSelected =
|
|
272
|
-
data.length > 0 &&
|
|
273
|
-
data.every((item) => selectedItems.includes(item));
|
|
274
|
-
if (allSelected) {
|
|
275
|
-
// Deselect all items
|
|
276
|
-
updateSelectedItems([]);
|
|
277
|
-
} else {
|
|
278
|
-
// Select all items
|
|
279
|
-
updateSelectedItems([...data]);
|
|
280
|
-
}
|
|
281
|
-
} else if (
|
|
282
|
-
selectHeadings &&
|
|
283
|
-
groupBy &&
|
|
284
|
-
isGroupHeading(newSelectedItem)
|
|
285
|
-
) {
|
|
286
|
-
// Handle heading selection - select/deselect all items in the group
|
|
287
|
-
const groupLabel = newSelectedItem.label;
|
|
288
|
-
const groupItems = data.filter(
|
|
289
|
-
(item) => groupBy(item) === groupLabel
|
|
290
|
-
);
|
|
291
|
-
const allSelected =
|
|
292
|
-
groupItems.length > 0 &&
|
|
293
|
-
groupItems.every((item) => selectedItems.includes(item));
|
|
294
|
-
|
|
295
|
-
if (allSelected) {
|
|
296
|
-
// Deselect all items in the group
|
|
297
|
-
updateSelectedItems(
|
|
298
|
-
selectedItems.filter((item) => !groupItems.includes(item))
|
|
299
|
-
);
|
|
300
|
-
} else {
|
|
301
|
-
// Select all items in the group
|
|
302
|
-
const newItems = [
|
|
303
|
-
...selectedItems.filter((item) => !groupItems.includes(item)),
|
|
304
|
-
...groupItems,
|
|
305
|
-
];
|
|
306
|
-
updateSelectedItems(newItems);
|
|
307
|
-
}
|
|
308
|
-
} else if (
|
|
309
|
-
!isGroupHeading(newSelectedItem) &&
|
|
310
|
-
!isToggleItem(newSelectedItem)
|
|
311
|
-
) {
|
|
312
|
-
// Only handle regular data items
|
|
313
|
-
const itemIndex = selectedItems.findIndex(
|
|
314
|
-
(item) => itemToKey(item) === itemToKey(newSelectedItem as T)
|
|
315
|
-
);
|
|
316
|
-
if (itemIndex >= 0) {
|
|
317
|
-
updateSelectedItems(
|
|
318
|
-
selectedItems.filter((_, index) => index !== itemIndex)
|
|
319
|
-
);
|
|
320
|
-
} else {
|
|
321
|
-
updateSelectedItems([...selectedItems, newSelectedItem as T]);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
break;
|
|
326
|
-
|
|
327
|
-
case useCombobox.stateChangeTypes.InputChange:
|
|
328
|
-
updateInputValue(newInputValue ?? "");
|
|
329
|
-
break;
|
|
330
|
-
|
|
331
|
-
default:
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
const {
|
|
337
|
-
isOpen: comboboxIsOpen,
|
|
338
|
-
getToggleButtonProps,
|
|
339
|
-
getLabelProps,
|
|
340
|
-
getMenuProps,
|
|
341
|
-
getInputProps,
|
|
342
|
-
highlightedIndex: comboboxHighlightedIndex,
|
|
343
|
-
getItemProps,
|
|
344
|
-
selectedItem,
|
|
345
|
-
} = comboboxProps;
|
|
346
|
-
|
|
347
|
-
// Map the highlighted index from combobox (which only knows about items) to the full items array (which includes headings)
|
|
348
|
-
const highlightedIndex = useHighlightedIndex(
|
|
349
|
-
items,
|
|
350
|
-
itemsForCombobox,
|
|
351
|
-
comboboxHighlightedIndex,
|
|
352
|
-
!!groupBy,
|
|
353
|
-
selectHeadings ?? false
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
const inputProps = getInputProps({ preventKeyAction: isOpen });
|
|
357
|
-
// Downshift adds aria-labelledby by default, which can conflict with our Label usage
|
|
358
|
-
inputProps["aria-labelledby"] = undefined;
|
|
359
|
-
if (!comboboxIsOpen) {
|
|
360
|
-
// Remove aria-controls when the menu is closed to avoid referencing a non-existent element
|
|
361
|
-
// @ts-expect-error downshift types require aria-controls even though it is not required
|
|
362
|
-
inputProps["aria-controls"] = undefined;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
366
|
-
// Call any provided onBlur handler
|
|
367
|
-
if (inputProps.onBlur) {
|
|
368
|
-
inputProps.onBlur(e);
|
|
369
|
-
}
|
|
370
|
-
// Don't close if the blur was caused by a mousedown inside the popover (e.g. clicking the scrollbar)
|
|
371
|
-
// Re-focus the input so future blur events can still close the menu
|
|
372
|
-
if (mousedownInPopoverRef.current) {
|
|
373
|
-
mousedownInPopoverRef.current = false;
|
|
374
|
-
e.target.focus();
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
// Need to figure why onblur is getting called when selecting select all item
|
|
378
|
-
updateIsOpen(false);
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const inputPropsToSpread = { ...inputProps };
|
|
382
|
-
// Remove onBlur from inputProps
|
|
383
|
-
// The blur event needs added to the root of token input
|
|
384
|
-
delete inputPropsToSpread.onBlur;
|
|
385
|
-
|
|
386
|
-
return (
|
|
387
|
-
<Popout
|
|
388
|
-
open={comboboxIsOpen}
|
|
389
|
-
onOpenChange={updateIsOpen}
|
|
390
|
-
side={popoutPlacement}
|
|
391
|
-
onOpenAutoFocus={(e) => {
|
|
392
|
-
// Prevent default auto-focus behavior
|
|
393
|
-
e.preventDefault();
|
|
394
|
-
}}
|
|
395
|
-
onCloseAutoFocus={(e) => {
|
|
396
|
-
// Prevent default auto-focus behavior
|
|
397
|
-
e.preventDefault();
|
|
398
|
-
}}
|
|
399
|
-
onPointerDownOutside={onPointerDownOutside}
|
|
400
|
-
onInteractOutside={onInteractOutside}
|
|
401
|
-
animationConfig={menuAnimationConfig}
|
|
402
|
-
onMouseDown={() => {
|
|
403
|
-
mousedownInPopoverRef.current = true;
|
|
404
|
-
}}
|
|
405
|
-
onMouseUp={() => {
|
|
406
|
-
mousedownInPopoverRef.current = false;
|
|
407
|
-
}}
|
|
408
|
-
content={
|
|
409
|
-
<ComboboxContent
|
|
410
|
-
items={items}
|
|
411
|
-
getMenuProps={getMenuProps}
|
|
412
|
-
getItemProps={(props: { index: number }) => {
|
|
413
|
-
// Only apply getItemProps to actual items, not headings or ToggleItems
|
|
414
|
-
const item = items[props.index];
|
|
415
|
-
if (isToggleItem(item)) {
|
|
416
|
-
// Handle ToggleItem clicks manually
|
|
417
|
-
return {
|
|
418
|
-
onClick: () => {
|
|
419
|
-
const allSelected =
|
|
420
|
-
data.length > 0 &&
|
|
421
|
-
data.every((item) => selectedItems.includes(item));
|
|
422
|
-
if (allSelected) {
|
|
423
|
-
updateSelectedItems([]);
|
|
424
|
-
} else {
|
|
425
|
-
updateSelectedItems([...data]);
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
const dataItem = item as T;
|
|
431
|
-
// Find the index in itemsForCombobox by comparing keys
|
|
432
|
-
const itemIndex = itemsForCombobox.indexOf(dataItem);
|
|
433
|
-
if (itemIndex === -1) {
|
|
434
|
-
return {};
|
|
435
|
-
}
|
|
436
|
-
return getItemProps({ item: dataItem, index: itemIndex });
|
|
437
|
-
}}
|
|
438
|
-
selectedItems={selectedItems}
|
|
439
|
-
highlightedIndex={highlightedIndex ?? -1}
|
|
440
|
-
renderItem={renderItem}
|
|
441
|
-
renderGroupHeading={renderGroupHeading}
|
|
442
|
-
EmptyState={EmptyState}
|
|
443
|
-
itemToKey={itemToKey}
|
|
444
|
-
itemToString={itemToString}
|
|
445
|
-
inputType={inputType}
|
|
446
|
-
selectHeadings={selectHeadings}
|
|
447
|
-
onHeadingClick={handleHeadingClick}
|
|
448
|
-
groupBy={groupBy}
|
|
449
|
-
allData={data}
|
|
450
|
-
selectAllItem={selectAllItem}
|
|
451
|
-
isVirtualized={isVirtualized}
|
|
452
|
-
estimatedItemHeight={estimatedItemHeight}
|
|
453
|
-
{...menuProps}
|
|
454
|
-
/>
|
|
455
|
-
}
|
|
456
|
-
>
|
|
457
|
-
<TokenInput
|
|
458
|
-
placeholder={placeholder}
|
|
459
|
-
inputProps={{
|
|
460
|
-
...inputPropsToSpread,
|
|
461
|
-
autoComplete: "off", // Override with "off" to prevent browser autocomplete
|
|
462
|
-
}}
|
|
463
|
-
tokens={selectedItems.map((item) => ({
|
|
464
|
-
id: String(itemToKey(item)),
|
|
465
|
-
value: itemToString(item),
|
|
466
|
-
}))}
|
|
467
|
-
onRemoveToken={(tokenId) => {
|
|
468
|
-
const itemToRemove = selectedItems.find(
|
|
469
|
-
(item) => String(itemToKey(item)) === tokenId
|
|
470
|
-
);
|
|
471
|
-
if (itemToRemove) {
|
|
472
|
-
removeSelectedItem(itemToRemove);
|
|
473
|
-
}
|
|
474
|
-
}}
|
|
475
|
-
onClickToken={(e) => {
|
|
476
|
-
e.stopPropagation();
|
|
477
|
-
}}
|
|
478
|
-
onBlur={handleBlur}
|
|
479
|
-
aria-controls={undefined}
|
|
480
|
-
aria-expanded={undefined}
|
|
481
|
-
aria-haspopup={undefined}
|
|
482
|
-
// @ts-expect-error Popout adds the type=button prop but TokenInput does not expect that
|
|
483
|
-
type={undefined}
|
|
484
|
-
/>
|
|
485
|
-
</Popout>
|
|
486
|
-
);
|
|
487
|
-
}
|