@sproutsocial/seeds-react-menu 1.7.12 → 1.7.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-menu",
3
- "version": "1.7.12",
3
+ "version": "1.7.13",
4
4
  "description": "Seeds React Menu",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -1,6 +1,5 @@
1
1
  import { useState, useMemo } from "react";
2
2
  import { useCombobox, useMultipleSelection } from "downshift";
3
- import { Label } from "@sproutsocial/seeds-react-label";
4
3
  import { TokenInput } from "@sproutsocial/seeds-react-token-input";
5
4
  // This will eventually be imported from seeds-react-popout
6
5
  import { Popout } from "../Common-V2";
@@ -26,6 +25,14 @@ function defaultReducer(state, actionAndChanges) {
26
25
  const { changes, type } = actionAndChanges;
27
26
 
28
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
+ };
29
36
  case useCombobox.stateChangeTypes.InputKeyDownEnter:
30
37
  case useCombobox.stateChangeTypes.ItemClick:
31
38
  // If the item is already selected, don't set highlighted index to 0
@@ -264,7 +271,6 @@ export function MultiAutocomplete<T extends DataWithId>({
264
271
  // Select all items
265
272
  updateSelectedItems([...data]);
266
273
  }
267
- updateInputValue("");
268
274
  } else if (
269
275
  selectHeadings &&
270
276
  groupBy &&
@@ -292,7 +298,6 @@ export function MultiAutocomplete<T extends DataWithId>({
292
298
  ];
293
299
  updateSelectedItems(newItems);
294
300
  }
295
- updateInputValue("");
296
301
  } else if (
297
302
  !isGroupHeading(newSelectedItem) &&
298
303
  !isToggleItem(newSelectedItem)
@@ -308,7 +313,6 @@ export function MultiAutocomplete<T extends DataWithId>({
308
313
  } else {
309
314
  updateSelectedItems([...selectedItems, newSelectedItem as T]);
310
315
  }
311
- updateInputValue("");
312
316
  }
313
317
  }
314
318
  break;
@@ -356,6 +360,8 @@ export function MultiAutocomplete<T extends DataWithId>({
356
360
  if (inputProps.onBlur) {
357
361
  inputProps.onBlur(e);
358
362
  }
363
+ // Need to figure why onblur is getting called when selecting select all item
364
+ updateIsOpen(false);
359
365
  };
360
366
 
361
367
  const inputPropsToSpread = { ...inputProps };
@@ -399,7 +405,7 @@ export function MultiAutocomplete<T extends DataWithId>({
399
405
  };
400
406
  }
401
407
  const dataItem = item as T;
402
- // Find the index in itemsForCombobox
408
+ // Find the index in itemsForCombobox by comparing keys
403
409
  const itemIndex = itemsForCombobox.indexOf(dataItem);
404
410
  if (itemIndex === -1) {
405
411
  return {};
@@ -424,7 +430,6 @@ export function MultiAutocomplete<T extends DataWithId>({
424
430
  />
425
431
  }
426
432
  >
427
- {/* @ts-expect-error onClick types to not match tokeninput */}
428
433
  <TokenInput
429
434
  placeholder={placeholder}
430
435
  inputProps={{
@@ -447,6 +452,11 @@ export function MultiAutocomplete<T extends DataWithId>({
447
452
  e.stopPropagation();
448
453
  }}
449
454
  onBlur={handleBlur}
455
+ aria-controls={undefined}
456
+ aria-expanded={undefined}
457
+ aria-haspopup={undefined}
458
+ // @ts-expect-error Popout adds the type=button prop but TokenInput does not expect that
459
+ type={undefined}
450
460
  />
451
461
  </Popout>
452
462
  );
@@ -7,6 +7,7 @@ import { Input } from "@sproutsocial/seeds-react-input";
7
7
  import { Popout } from "../Common-V2";
8
8
  import {
9
9
  isGroupHeading,
10
+ isToggleItem,
10
11
  menuAnimationConfig,
11
12
  getFilteredItems,
12
13
  groupItemsWithHeadings,
@@ -96,19 +97,22 @@ export function SingleAutocomplete<T extends DataWithId>({
96
97
  const isIsOpenControlled = controlledIsOpen !== undefined;
97
98
  const isOpen = isIsOpenControlled ? controlledIsOpen : uncontrolledIsOpen;
98
99
 
99
- // TODO: This does not actually update the input value when the selected item changes externally
100
- // Sync inputValue with selectedItem when selectedItem changes externally and inputValue is not controlled
100
+ // Sync inputValue with selectedItem when selectedItem changes externally and menu is closed
101
101
  useEffect(() => {
102
102
  if (selectedItem && !isOpen) {
103
103
  // Only update if menu is closed - when open, user might be typing
104
104
  const selectedItemString = itemToString(selectedItem);
105
- if (
106
- !isInputValueControlled &&
107
- uncontrolledInputValue !== selectedItemString
108
- ) {
109
- setUncontrolledInputValue(selectedItemString);
110
- } else if (isInputValueControlled && inputValue !== selectedItemString) {
111
- updateInputValue(selectedItemString);
105
+ if (!isInputValueControlled) {
106
+ // For uncontrolled input, update internal state
107
+ if (uncontrolledInputValue !== selectedItemString) {
108
+ setUncontrolledInputValue(selectedItemString);
109
+ }
110
+ } else {
111
+ // For controlled input, notify parent if value doesn't match
112
+ // Parent is responsible for updating the controlled inputValue
113
+ if (inputValue !== selectedItemString) {
114
+ onInputValueChange?.(selectedItemString);
115
+ }
112
116
  }
113
117
  }
114
118
  }, [
@@ -117,6 +121,8 @@ export function SingleAutocomplete<T extends DataWithId>({
117
121
  isInputValueControlled,
118
122
  itemToString,
119
123
  uncontrolledInputValue,
124
+ inputValue,
125
+ onInputValueChange,
120
126
  ]);
121
127
 
122
128
  const updateSelectedItem = (newSelectedItem: T | null) => {
@@ -218,10 +224,7 @@ export function SingleAutocomplete<T extends DataWithId>({
218
224
  isOpen: newIsOpen,
219
225
  }) {
220
226
  // Handle isOpen changes for any state change type
221
- if (
222
- newIsOpen !== undefined &&
223
- type !== useCombobox.stateChangeTypes.InputBlur
224
- ) {
227
+ if (newIsOpen !== undefined) {
225
228
  updateIsOpen(newIsOpen);
226
229
  }
227
230
 
@@ -284,15 +287,6 @@ export function SingleAutocomplete<T extends DataWithId>({
284
287
  }
285
288
  const { ref, id: inputId, ...restProps } = inputProps as any;
286
289
 
287
- const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
288
- // Call any provided onBlur handler
289
- if (inputProps.onBlur) {
290
- inputProps.onBlur(e);
291
- }
292
- // Close the menu on blur
293
- updateIsOpen(false);
294
- };
295
-
296
290
  return (
297
291
  <Popout
298
292
  open={comboboxIsOpen}
@@ -314,8 +308,13 @@ export function SingleAutocomplete<T extends DataWithId>({
314
308
  // Only apply getItemProps to actual items, not headings or ToggleItems
315
309
  const item = items[props.index];
316
310
  const dataItem = item as T;
317
- // Find the index in itemsForCombobox
318
- const itemIndex = itemsForCombobox.indexOf(dataItem);
311
+ // Find the index in itemsForCombobox using itemToKey for safer comparison
312
+ const itemIndex = itemsForCombobox.findIndex((comboboxItem) => {
313
+ if (isToggleItem(comboboxItem)) {
314
+ return false;
315
+ }
316
+ return itemToKey(comboboxItem as T) === itemToKey(dataItem);
317
+ });
319
318
  if (itemIndex === -1) {
320
319
  return {};
321
320
  }
@@ -340,9 +339,7 @@ export function SingleAutocomplete<T extends DataWithId>({
340
339
  <Input
341
340
  id={id}
342
341
  inputProps={{
343
- ...restProps,
344
342
  autoComplete: "off",
345
- onBlur: handleBlur,
346
343
  }}
347
344
  innerRef={ref}
348
345
  placeholder={placeholder}
@@ -355,7 +352,6 @@ export function SingleAutocomplete<T extends DataWithId>({
355
352
  updateInputValue("");
356
353
  }}
357
354
  {...restProps}
358
- onBlur={handleBlur}
359
355
  />
360
356
  </Popout>
361
357
  );
@@ -78,7 +78,9 @@ export function Popout({
78
78
  <Popover.Trigger ref={radixRef} asChild>
79
79
  {children}
80
80
  </Popover.Trigger>
81
- <Popover.Anchor />
81
+ <Popover.Anchor>
82
+ <div style={{ height: "1px" }}>&nbsp;</div>
83
+ </Popover.Anchor>
82
84
  <Popover.Portal>
83
85
  <StyledContent
84
86
  side={side}
@@ -367,8 +367,13 @@ export function MultiSelect<T extends DataWithId>({
367
367
  };
368
368
  }
369
369
  const dataItem = item as T;
370
- // Find the index in itemsForSelect
371
- const itemIndex = itemsForSelect.indexOf(dataItem);
370
+ // Find the index in itemsForSelect using itemToKey for safer comparison
371
+ const itemIndex = itemsForSelect.findIndex((selectItem) => {
372
+ if (isGroupHeading(selectItem) || isToggleItem(selectItem)) {
373
+ return false;
374
+ }
375
+ return itemToKey(selectItem as T) === itemToKey(dataItem);
376
+ });
372
377
  if (itemIndex === -1) {
373
378
  return {};
374
379
  }
@@ -239,8 +239,13 @@ export function SingleSelect<T extends DataWithId>({
239
239
  };
240
240
  }
241
241
  const dataItem = item as T;
242
- // Find the index in itemsForSelect
243
- const itemIndex = itemsForSelect.indexOf(dataItem);
242
+ // Find the index in itemsForSelect using itemToKey for safer comparison
243
+ const itemIndex = itemsForSelect.findIndex((selectItem) => {
244
+ if (isToggleItem(selectItem)) {
245
+ return false;
246
+ }
247
+ return itemToKey(selectItem as T) === itemToKey(dataItem);
248
+ });
244
249
  if (itemIndex === -1) {
245
250
  return {};
246
251
  }