@carbon/react 1.60.2 → 1.61.0-rc.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.
Files changed (34) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +972 -1013
  2. package/es/components/ComboBox/ComboBox.js +8 -8
  3. package/es/components/ComposedModal/ComposedModal.js +4 -2
  4. package/es/components/DatePicker/DatePicker.js +6 -1
  5. package/es/components/Dropdown/Dropdown.d.ts +4 -0
  6. package/es/components/Dropdown/Dropdown.js +53 -8
  7. package/es/components/Modal/Modal.js +4 -2
  8. package/es/components/MultiSelect/FilterableMultiSelect.d.ts +20 -14
  9. package/es/components/MultiSelect/FilterableMultiSelect.js +59 -8
  10. package/es/components/MultiSelect/MultiSelect.d.ts +7 -38
  11. package/es/components/MultiSelect/MultiSelect.js +52 -5
  12. package/es/components/MultiSelect/MultiSelectPropTypes.d.ts +19 -16
  13. package/es/components/OverflowMenu/OverflowMenu.js +0 -1
  14. package/es/components/OverflowMenu/next/index.d.ts +4 -0
  15. package/es/components/OverflowMenu/next/index.js +41 -2
  16. package/es/components/UIShell/HeaderContainer.d.ts +11 -9
  17. package/es/components/UIShell/HeaderContainer.js +9 -7
  18. package/lib/components/ComboBox/ComboBox.js +7 -7
  19. package/lib/components/ComposedModal/ComposedModal.js +6 -4
  20. package/lib/components/DatePicker/DatePicker.js +6 -1
  21. package/lib/components/Dropdown/Dropdown.d.ts +4 -0
  22. package/lib/components/Dropdown/Dropdown.js +49 -4
  23. package/lib/components/Modal/Modal.js +6 -4
  24. package/lib/components/MultiSelect/FilterableMultiSelect.d.ts +20 -14
  25. package/lib/components/MultiSelect/FilterableMultiSelect.js +56 -5
  26. package/lib/components/MultiSelect/MultiSelect.d.ts +7 -38
  27. package/lib/components/MultiSelect/MultiSelect.js +49 -2
  28. package/lib/components/MultiSelect/MultiSelectPropTypes.d.ts +19 -16
  29. package/lib/components/OverflowMenu/OverflowMenu.js +0 -1
  30. package/lib/components/OverflowMenu/next/index.d.ts +4 -0
  31. package/lib/components/OverflowMenu/next/index.js +40 -1
  32. package/lib/components/UIShell/HeaderContainer.d.ts +11 -9
  33. package/lib/components/UIShell/HeaderContainer.js +9 -7
  34. package/package.json +7 -7
@@ -9,7 +9,7 @@ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js
9
9
  import cx from 'classnames';
10
10
  import { useCombobox } from 'downshift';
11
11
  import PropTypes from 'prop-types';
12
- import React__default, { forwardRef, useEffect, useContext, useRef, useState } from 'react';
12
+ import React__default, { forwardRef, useEffect, useContext, useRef, useState, useMemo } from 'react';
13
13
  import '../Text/index.js';
14
14
  import { WarningFilled, WarningAltFilled, Checkmark } from '@carbon/icons-react';
15
15
  import ListBox from '../ListBox/index.js';
@@ -382,6 +382,10 @@ const ComboBox = /*#__PURE__*/forwardRef((props, ref) => {
382
382
  // when both the message is supplied *and* when the component is in
383
383
  // the matching state (invalid, warn, etc).
384
384
  const ariaDescribedBy = invalid && invalidText && invalidTextId || warn && warnText && warnTextId || helperText && !isFluid && helperTextId || undefined;
385
+ const menuProps = useMemo(() => getMenuProps({
386
+ 'aria-label': deprecatedAriaLabel || ariaLabel,
387
+ ref: autoAlign ? refs.setFloating : null
388
+ }), [autoAlign, deprecatedAriaLabel, ariaLabel]);
385
389
  return /*#__PURE__*/React__default.createElement("div", {
386
390
  className: wrapperClasses
387
391
  }, titleText && /*#__PURE__*/React__default.createElement(Text, _extends({
@@ -399,7 +403,7 @@ const ComboBox = /*#__PURE__*/forwardRef((props, ref) => {
399
403
  light: light,
400
404
  size: size,
401
405
  warn: warn,
402
- ref: refs.setReference,
406
+ ref: autoAlign ? refs.setReference : null,
403
407
  warnText: warnText,
404
408
  warnTextId: warnTextId
405
409
  }, /*#__PURE__*/React__default.createElement("div", {
@@ -412,7 +416,7 @@ const ComboBox = /*#__PURE__*/forwardRef((props, ref) => {
412
416
  "aria-haspopup": "listbox",
413
417
  title: textInput?.current?.value
414
418
  }, getInputProps({
415
- 'aria-controls': isOpen ? undefined : getMenuProps().id,
419
+ 'aria-controls': isOpen ? undefined : menuProps.id,
416
420
  placeholder,
417
421
  ref: {
418
422
  ...mergeRefs(textInput, ref)
@@ -474,11 +478,7 @@ const ComboBox = /*#__PURE__*/forwardRef((props, ref) => {
474
478
  // @ts-expect-error
475
479
  isOpen: isOpen,
476
480
  translateWithId: translateWithId
477
- }))), normalizedSlug, /*#__PURE__*/React__default.createElement(ListBox.Menu, _extends({}, getMenuProps({
478
- 'aria-label': deprecatedAriaLabel || ariaLabel
479
- }), {
480
- ref: mergeRefs(getMenuProps().ref, refs.setFloating)
481
- }), isOpen ? filterItems(items, itemToString, inputValue).map((item, index) => {
481
+ }))), normalizedSlug, /*#__PURE__*/React__default.createElement(ListBox.Menu, menuProps, isOpen ? filterItems(items, itemToString, inputValue).map((item, index) => {
482
482
  const isObject = item !== null && typeof item === 'object';
483
483
  const title = isObject && 'text' in item && itemToElement ? item.text?.toString() : itemToString(item);
484
484
  const itemProps = getItemProps({
@@ -9,6 +9,7 @@ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js
9
9
  import React__default, { useRef, useState, useEffect } from 'react';
10
10
  import { isElement } from 'react-is';
11
11
  import PropTypes from 'prop-types';
12
+ import { Layer } from '../Layer/index.js';
12
13
  import { ModalHeader } from './ModalHeader.js';
13
14
  import { ModalFooter } from './ModalFooter.js';
14
15
  import debounce from 'lodash.debounce';
@@ -59,7 +60,7 @@ const ModalBody = /*#__PURE__*/React__default.forwardRef(function ModalBody(_ref
59
60
  tabIndex: 0,
60
61
  role: 'region'
61
62
  } : {};
62
- return /*#__PURE__*/React__default.createElement("div", _extends({
63
+ return /*#__PURE__*/React__default.createElement(Layer, _extends({
63
64
  className: contentClass
64
65
  }, hasScrollingContentProps, rest, {
65
66
  ref: mergeRefs(contentRef, ref)
@@ -249,7 +250,8 @@ const ComposedModal = /*#__PURE__*/React__default.forwardRef(function ComposedMo
249
250
  size: 'sm'
250
251
  });
251
252
  }
252
- return /*#__PURE__*/React__default.createElement("div", _extends({}, rest, {
253
+ return /*#__PURE__*/React__default.createElement(Layer, _extends({}, rest, {
254
+ level: 0,
253
255
  role: "presentation",
254
256
  ref: ref,
255
257
  "aria-hidden": !open,
@@ -176,6 +176,8 @@ const DatePicker = /*#__PURE__*/React__default.forwardRef(function DatePicker(_r
176
176
 
177
177
  // fix datepicker deleting the selectedDate when the calendar closes
178
178
  const onCalendarClose = (selectedDates, dateStr) => {
179
+ endInputField?.current?.focus();
180
+ calendarRef?.current?.calendarContainer?.classList.remove('open');
179
181
  setTimeout(() => {
180
182
  if (lastStartValue.current && selectedDates[0] && !startInputField.current.value) {
181
183
  startInputField.current.value = lastStartValue.current;
@@ -355,9 +357,12 @@ const DatePicker = /*#__PURE__*/React__default.forwardRef(function DatePicker(_r
355
357
  calendarRef.current = calendar;
356
358
  function handleArrowDown(event) {
357
359
  if (match(event, Escape)) {
358
- calendar.calendarContainer.classList.remove('open');
360
+ calendar?.calendarContainer?.classList.remove('open');
359
361
  }
360
362
  if (match(event, ArrowDown)) {
363
+ if (event.target == endInputField.current) {
364
+ calendar?.calendarContainer?.classList.add('open');
365
+ }
361
366
  const {
362
367
  calendarContainer,
363
368
  selectedDateElem: fpSelectedDateElem,
@@ -23,6 +23,10 @@ export interface DropdownProps<ItemType> extends Omit<ReactAttr<HTMLDivElement>,
23
23
  * Specify a label to be read by screen readers on the container note.
24
24
  */
25
25
  ariaLabel?: string;
26
+ /**
27
+ * **Experimental**: Will attempt to automatically align the floating element to avoid collisions with the viewport and being clipped by ancestor elements.
28
+ */
29
+ autoAlign?: boolean;
26
30
  /**
27
31
  * Specify the direction of the dropdown. Can be either top or bottom.
28
32
  */
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
- import React__default, { useContext, useRef, useState } from 'react';
9
+ import React__default, { useEffect, useContext, useRef, useState } from 'react';
10
10
  import { useSelect } from 'downshift';
11
11
  import cx from 'classnames';
12
12
  import PropTypes from 'prop-types';
@@ -18,6 +18,7 @@ import { usePrefix } from '../../internal/usePrefix.js';
18
18
  import '../FluidForm/FluidForm.js';
19
19
  import { FormContext } from '../FluidForm/FormContext.js';
20
20
  import setupGetInstanceId from '../../tools/setupGetInstanceId.js';
21
+ import { useFloating, size, flip, autoUpdate } from '@floating-ui/react';
21
22
  import { ListBoxSize, ListBoxType } from '../ListBox/ListBoxPropTypes.js';
22
23
 
23
24
  const getInstanceId = setupGetInstanceId();
@@ -43,6 +44,7 @@ const defaultItemToString = item => {
43
44
  };
44
45
  const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
45
46
  let {
47
+ autoAlign = false,
46
48
  className: containerClassName,
47
49
  disabled = false,
48
50
  direction = 'bottom',
@@ -54,7 +56,7 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
54
56
  itemToElement = null,
55
57
  renderSelectedItem,
56
58
  type = 'default',
57
- size,
59
+ size: size$1,
58
60
  onChange,
59
61
  id,
60
62
  titleText = '',
@@ -73,6 +75,40 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
73
75
  slug,
74
76
  ...other
75
77
  } = _ref;
78
+ const {
79
+ refs,
80
+ floatingStyles
81
+ } = useFloating(autoAlign ? {
82
+ placement: direction,
83
+ // The floating element is positioned relative to its nearest
84
+ // containing block (usually the viewport). It will in many cases also
85
+ // “break” the floating element out of a clipping ancestor.
86
+ // https://floating-ui.com/docs/misc#clipping
87
+ strategy: 'fixed',
88
+ // Middleware order matters, arrow should be last
89
+ middleware: [size({
90
+ apply(_ref2) {
91
+ let {
92
+ rects,
93
+ elements
94
+ } = _ref2;
95
+ Object.assign(elements.floating.style, {
96
+ width: `${rects.reference.width}px`
97
+ });
98
+ }
99
+ }), flip()],
100
+ whileElementsMounted: autoUpdate
101
+ } : {} // When autoAlign is turned off, floating-ui will not be used
102
+ );
103
+ useEffect(() => {
104
+ if (autoAlign) {
105
+ Object.keys(floatingStyles).forEach(style => {
106
+ if (refs.floating.current) {
107
+ refs.floating.current.style[style] = floatingStyles[style];
108
+ }
109
+ });
110
+ }
111
+ }, [floatingStyles, autoAlign, refs.floating]);
76
112
  const prefix = usePrefix();
77
113
  const {
78
114
  isFluid
@@ -146,8 +182,9 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
146
182
  [`${prefix}--dropdown--disabled`]: disabled,
147
183
  [`${prefix}--dropdown--light`]: light,
148
184
  [`${prefix}--dropdown--readonly`]: readOnly,
149
- [`${prefix}--dropdown--${size}`]: size,
150
- [`${prefix}--list-box--up`]: direction === 'top'
185
+ [`${prefix}--dropdown--${size$1}`]: size$1,
186
+ [`${prefix}--list-box--up`]: direction === 'top',
187
+ [`${prefix}--dropdown--autoalign`]: autoAlign
151
188
  });
152
189
  const titleClasses = cx(`${prefix}--label`, {
153
190
  [`${prefix}--label--disabled`]: disabled,
@@ -174,10 +211,10 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
174
211
  id: helperId,
175
212
  className: helperClasses
176
213
  }, helperText) : null;
177
- function onSelectedItemChange(_ref2) {
214
+ function onSelectedItemChange(_ref3) {
178
215
  let {
179
216
  selectedItem
180
- } = _ref2;
217
+ } = _ref3;
181
218
  if (onChange) {
182
219
  onChange({
183
220
  selectedItem: selectedItem ?? null
@@ -227,6 +264,7 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
227
264
  }
228
265
  };
229
266
  const menuProps = getMenuProps();
267
+ const menuRef = mergeRefs(menuProps.ref, refs.setFloating);
230
268
 
231
269
  // Slug is always size `mini`
232
270
  let normalizedSlug;
@@ -243,7 +281,7 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
243
281
  onFocus: handleFocus,
244
282
  onBlur: handleFocus,
245
283
  "aria-label": deprecatedAriaLabel || ariaLabel,
246
- size: size,
284
+ size: size$1,
247
285
  className: className,
248
286
  invalid: invalid,
249
287
  invalidText: invalidText,
@@ -251,6 +289,7 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
251
289
  warnText: warnText,
252
290
  light: light,
253
291
  isOpen: isOpen,
292
+ ref: refs.setReference,
254
293
  id: id
255
294
  }, invalid && /*#__PURE__*/React__default.createElement(WarningFilled, {
256
295
  className: `${prefix}--list-box__invalid-icon`
@@ -273,7 +312,9 @@ const Dropdown = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
273
312
  }, selectedItem ? renderSelectedItem ? renderSelectedItem(selectedItem) : itemToString(selectedItem) : label), /*#__PURE__*/React__default.createElement(ListBox.MenuIcon, {
274
313
  isOpen: isOpen,
275
314
  translateWithId: translateWithId
276
- })), normalizedSlug, /*#__PURE__*/React__default.createElement(ListBox.Menu, menuProps, isOpen && items.map((item, index) => {
315
+ })), normalizedSlug, /*#__PURE__*/React__default.createElement(ListBox.Menu, _extends({}, menuProps, {
316
+ ref: menuRef
317
+ }), isOpen && items.map((item, index) => {
277
318
  const isObject = item !== null && typeof item === 'object';
278
319
  const itemProps = getItemProps({
279
320
  item,
@@ -308,6 +349,10 @@ Dropdown.propTypes = {
308
349
  * Specify a label to be read by screen readers on the container note.
309
350
  */
310
351
  ariaLabel: deprecate(PropTypes.string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'),
352
+ /**
353
+ * **Experimental**: Will attempt to automatically align the floating element to avoid collisions with the viewport and being clipped by ancestor elements.
354
+ */
355
+ autoAlign: PropTypes.bool,
311
356
  /**
312
357
  * Provide a custom className to be applied on the cds--dropdown node
313
358
  */
@@ -15,6 +15,7 @@ import Button from '../Button/Button.js';
15
15
  import '../Button/Button.Skeleton.js';
16
16
  import ButtonSet from '../ButtonSet/ButtonSet.js';
17
17
  import InlineLoading from '../InlineLoading/InlineLoading.js';
18
+ import { Layer } from '../Layer/index.js';
18
19
  import requiredIfGivenPropIsTruthy from '../../prop-types/requiredIfGivenPropIsTruthy.js';
19
20
  import wrapFocus, { wrapFocusWithoutSentinels, elementOrParentIsFloatingMenu } from '../../internal/wrapFocus.js';
20
21
  import debounce from 'lodash.debounce';
@@ -266,7 +267,7 @@ const Modal = /*#__PURE__*/React__default.forwardRef(function Modal(_ref, ref) {
266
267
  as: "h3",
267
268
  id: modalHeadingId,
268
269
  className: `${prefix}--modal-header__heading`
269
- }, modalHeading), normalizedSlug, !passiveModal && modalButton), /*#__PURE__*/React__default.createElement("div", _extends({
270
+ }, modalHeading), normalizedSlug, !passiveModal && modalButton), /*#__PURE__*/React__default.createElement(Layer, _extends({
270
271
  ref: contentRef,
271
272
  id: modalBodyId,
272
273
  className: contentClasses
@@ -301,7 +302,8 @@ const Modal = /*#__PURE__*/React__default.forwardRef(function Modal(_ref, ref) {
301
302
  className: `${prefix}--inline-loading--btn`,
302
303
  onSuccess: onLoadingSuccess
303
304
  }))));
304
- return /*#__PURE__*/React__default.createElement("div", _extends({}, rest, {
305
+ return /*#__PURE__*/React__default.createElement(Layer, _extends({}, rest, {
306
+ level: 0,
305
307
  onKeyDown: handleKeyDown,
306
308
  onMouseDown: handleMousedown,
307
309
  onBlur: !focusTrapWithoutSentinels ? handleBlur : () => {},
@@ -6,8 +6,8 @@
6
6
  */
7
7
  import { type UseComboboxProps, type UseMultipleSelectionProps } from 'downshift';
8
8
  import { ReactNode, FunctionComponent, ReactElement } from 'react';
9
- import { type ItemBase, type SortingPropTypes } from './MultiSelectPropTypes';
10
- export interface FilterableMultiSelectProps<Item extends ItemBase> extends SortingPropTypes<Item> {
9
+ import { type MultiSelectSortingProps } from './MultiSelectPropTypes';
10
+ export interface FilterableMultiSelectProps<ItemType> extends MultiSelectSortingProps<ItemType> {
11
11
  /**
12
12
  * Specify a label to be read by screen readers on the container node
13
13
  * @deprecated
@@ -15,6 +15,12 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
15
15
  'aria-label'?: string;
16
16
  /** @deprecated */
17
17
  ariaLabel?: string;
18
+ /**
19
+ * **Experimental**: Will attempt to automatically align the floating
20
+ * element to avoid collisions with the viewport and being clipped by
21
+ * ancestor elements.
22
+ */
23
+ autoAlign?: boolean;
18
24
  className?: string;
19
25
  /**
20
26
  * Specify the text that should be read for screen readers that describes total items selected
@@ -35,14 +41,14 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
35
41
  /**
36
42
  * Additional props passed to Downshift
37
43
  */
38
- downshiftProps?: UseMultipleSelectionProps<Item>;
44
+ downshiftProps?: UseMultipleSelectionProps<ItemType>;
39
45
  /**
40
46
  * Default sorter is assigned if not provided.
41
47
  */
42
- filterItems(items: readonly Item[], extra: {
48
+ filterItems(items: readonly ItemType[], extra: {
43
49
  inputValue: string | null;
44
- itemToString: NonNullable<UseMultipleSelectionProps<Item>['itemToString']>;
45
- }): Item[];
50
+ itemToString: NonNullable<UseMultipleSelectionProps<ItemType>['itemToString']>;
51
+ }): ItemType[];
46
52
  /**
47
53
  * Specify whether the title text should be hidden or not
48
54
  */
@@ -60,7 +66,7 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
60
66
  * Allow users to pass in arbitrary items from their collection that are
61
67
  * pre-selected
62
68
  */
63
- initialSelectedItems?: Item[];
69
+ initialSelectedItems?: ItemType[];
64
70
  /**
65
71
  * Is the current selection invalid?
66
72
  */
@@ -73,7 +79,7 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
73
79
  * Function to render items as custom components instead of strings.
74
80
  * Defaults to null and is overridden by a getter
75
81
  */
76
- itemToElement?: FunctionComponent<Item>;
82
+ itemToElement?: FunctionComponent<ItemType>;
77
83
  /**
78
84
  * Helper function passed to downshift that allows the library to render
79
85
  * a given item to a string label.
@@ -81,12 +87,12 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
81
87
  * By default, it extracts the `label` field from a given item
82
88
  * to serve as the item label in the list.
83
89
  */
84
- itemToString?(item: Item | null): string;
90
+ itemToString?(item: ItemType | null): string;
85
91
  /**
86
92
  * We try to stay as generic as possible here to allow individuals to pass
87
93
  * in a collection of whatever kind of data structure they prefer
88
94
  */
89
- items: Item[];
95
+ items: ItemType[];
90
96
  /**
91
97
  * @deprecated `true` to use the light version.
92
98
  */
@@ -102,13 +108,13 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
102
108
  * consuming component what kind of internal state changes are occurring.
103
109
  */
104
110
  onChange?(changes: {
105
- selectedItems: Item[];
111
+ selectedItems: ItemType[];
106
112
  }): void;
107
113
  /**
108
114
  * A utility for this controlled component
109
115
  * to communicate to the currently typed input.
110
116
  */
111
- onInputValueChange?: UseComboboxProps<Item>['onInputValueChange'];
117
+ onInputValueChange?: UseComboboxProps<ItemType>['onInputValueChange'];
112
118
  /**
113
119
  * `onMenuChange` is a utility for this controlled component to communicate to a
114
120
  * consuming component that the menu was opened(`true`)/closed(`false`).
@@ -133,7 +139,7 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
133
139
  /**
134
140
  * For full control of the selected items
135
141
  */
136
- selectedItems?: Item[];
142
+ selectedItems?: ItemType[];
137
143
  /**
138
144
  * Specify the size of the ListBox.
139
145
  * Currently, supports either `sm`, `md` or `lg` as an option.
@@ -167,7 +173,7 @@ export interface FilterableMultiSelectProps<Item extends ItemBase> extends Sorti
167
173
  warnText?: ReactNode;
168
174
  }
169
175
  declare const FilterableMultiSelect: {
170
- <Item extends ItemBase>(props: FilterableMultiSelectProps<Item>): ReactElement;
176
+ <ItemType>(props: FilterableMultiSelectProps<ItemType>): ReactElement;
171
177
  propTypes?: any;
172
178
  contextTypes?: any;
173
179
  defaultProps?: any;
@@ -11,7 +11,7 @@ import cx from 'classnames';
11
11
  import Downshift, { useCombobox, useMultipleSelection } from 'downshift';
12
12
  import isEqual from 'lodash.isequal';
13
13
  import PropTypes from 'prop-types';
14
- import React__default, { useContext, useState, useRef, useEffect } from 'react';
14
+ import React__default, { useContext, useState, useLayoutEffect, useRef, useEffect } from 'react';
15
15
  import { defaultFilterItems } from '../ComboBox/tools/filter.js';
16
16
  import { sortingPropTypes } from './MultiSelectPropTypes.js';
17
17
  import ListBox from '../ListBox/index.js';
@@ -24,6 +24,7 @@ import { usePrefix } from '../../internal/usePrefix.js';
24
24
  import '../FluidForm/FluidForm.js';
25
25
  import { FormContext } from '../FluidForm/FormContext.js';
26
26
  import { useSelection } from '../../internal/Selection.js';
27
+ import { useFloating, flip, size, autoUpdate } from '@floating-ui/react';
27
28
  import { match } from '../../internal/keyboard/match.js';
28
29
  import ListBoxSelection from '../ListBox/next/ListBoxSelection.js';
29
30
  import ListBoxTrigger from '../ListBox/next/ListBoxTrigger.js';
@@ -53,6 +54,7 @@ const {
53
54
  } = useMultipleSelection.stateChangeTypes;
54
55
  const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function FilterableMultiSelect(_ref, ref) {
55
56
  let {
57
+ autoAlign = false,
56
58
  className: containerClassName,
57
59
  clearSelectionDescription = 'Total items selected: ',
58
60
  clearSelectionText = 'To clear selection, press Delete or Backspace',
@@ -82,7 +84,7 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
82
84
  type,
83
85
  selectionFeedback = 'top-after-reopen',
84
86
  selectedItems: selected,
85
- size,
87
+ size: size$1,
86
88
  sortItems = defaultSortItems,
87
89
  translateWithId,
88
90
  useTitleInItem,
@@ -109,6 +111,42 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
109
111
  onChange,
110
112
  selectedItems: selected
111
113
  });
114
+ const {
115
+ refs,
116
+ floatingStyles,
117
+ middlewareData
118
+ } = useFloating(autoAlign ? {
119
+ placement: direction,
120
+ // The floating element is positioned relative to its nearest
121
+ // containing block (usually the viewport). It will in many cases also
122
+ // “break” the floating element out of a clipping ancestor.
123
+ // https://floating-ui.com/docs/misc#clipping
124
+ strategy: 'fixed',
125
+ // Middleware order matters, arrow should be last
126
+ middleware: [flip({
127
+ crossAxis: false
128
+ }), size({
129
+ apply(_ref2) {
130
+ let {
131
+ rects,
132
+ elements
133
+ } = _ref2;
134
+ Object.assign(elements.floating.style, {
135
+ width: `${rects.reference.width}px`
136
+ });
137
+ }
138
+ })],
139
+ whileElementsMounted: autoUpdate
140
+ } : {});
141
+ useLayoutEffect(() => {
142
+ if (autoAlign) {
143
+ Object.keys(floatingStyles).forEach(style => {
144
+ if (refs.floating.current) {
145
+ refs.floating.current.style[style] = floatingStyles[style];
146
+ }
147
+ });
148
+ }
149
+ }, [autoAlign, floatingStyles, refs.floating, middlewareData, open]);
112
150
  const textInput = useRef(null);
113
151
  const filterableMultiSelectInstanceId = useId();
114
152
  const prefix = usePrefix();
@@ -116,6 +154,8 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
116
154
  setIsOpen(open);
117
155
  setPrevOpen(open);
118
156
  }
157
+
158
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
119
159
  const sortedItems = sortItems(filterItems(items, {
120
160
  itemToString,
121
161
  inputValue
@@ -231,8 +271,6 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
231
271
  return changes;
232
272
  case InputBlur:
233
273
  case InputKeyDownEscape:
234
- setInputFocused(false);
235
- setInputValue('');
236
274
  setIsOpen(false);
237
275
  return changes;
238
276
  case FunctionToggleMenu:
@@ -416,7 +454,11 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
416
454
  $input.setSelectionRange($value.length, $value.length);
417
455
  }
418
456
  },
419
- onFocus: () => setInputFocused(true)
457
+ onFocus: () => setInputFocused(true),
458
+ onBlur: () => {
459
+ !isOpen && setInputFocused(false);
460
+ setInputValue('');
461
+ }
420
462
  }));
421
463
  const menuProps = getMenuProps({}, {
422
464
  suppressRefError: true
@@ -451,9 +493,10 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
451
493
  warn: warn,
452
494
  warnText: warnText,
453
495
  isOpen: isOpen,
454
- size: size
496
+ size: size$1
455
497
  }, /*#__PURE__*/React__default.createElement("div", {
456
- className: `${prefix}--list-box__field`
498
+ className: `${prefix}--list-box__field`,
499
+ ref: refs.setReference
457
500
  }, controlledSelectedItems.length > 0 &&
458
501
  /*#__PURE__*/
459
502
  // @ts-expect-error: It is expecting a non-required prop called: "onClearSelection"
@@ -494,7 +537,9 @@ const FilterableMultiSelect = /*#__PURE__*/React__default.forwardRef(function Fi
494
537
  // @ts-expect-error
495
538
  isOpen: isOpen,
496
539
  translateWithId: translateWithId
497
- }))), normalizedSlug, /*#__PURE__*/React__default.createElement(ListBox.Menu, menuProps, isOpen ? sortedItems.map((item, index) => {
540
+ }))), normalizedSlug, /*#__PURE__*/React__default.createElement(ListBox.Menu, _extends({}, menuProps, {
541
+ ref: refs.setFloating
542
+ }), isOpen ? sortedItems.map((item, index) => {
498
543
  const isChecked = controlledSelectedItems.filter(selected => isEqual(selected, item)).length > 0;
499
544
  const itemProps = getItemProps({
500
545
  item,
@@ -542,6 +587,12 @@ FilterableMultiSelect.propTypes = {
542
587
  * Specify a label to be read by screen readers on the container note.
543
588
  */
544
589
  ariaLabel: deprecate(PropTypes.string, 'ariaLabel / aria-label props are no longer required for FilterableMultiSelect'),
590
+ /**
591
+ * **Experimental**: Will attempt to automatically align the floating
592
+ * element to avoid collisions with the viewport and being clipped by
593
+ * ancestor elements.
594
+ */
595
+ autoAlign: PropTypes.bool,
545
596
  /**
546
597
  * Specify the text that should be read for screen readers that describes total items selected
547
598
  */
@@ -7,50 +7,19 @@
7
7
  import { UseSelectProps } from 'downshift';
8
8
  import React, { ReactNode } from 'react';
9
9
  import { ListBoxSize, ListBoxType } from '../ListBox';
10
+ import { MultiSelectSortingProps } from './MultiSelectPropTypes';
10
11
  import { ListBoxProps } from '../ListBox/ListBox';
11
12
  import type { InternationalProps } from '../../types/common';
12
- interface SharedOptions {
13
- locale: string;
14
- }
15
- interface DownshiftTypedProps<ItemType> {
16
- itemToString?(item: ItemType): string;
17
- }
18
- interface SortItemsOptions<ItemType> extends SharedOptions, DownshiftTypedProps<ItemType> {
19
- compareItems(item1: ItemType, item2: ItemType, options: SharedOptions): number;
20
- selectedItems: ItemType[];
21
- }
22
- interface MultiSelectSortingProps<ItemType> {
23
- /**
24
- * Provide a compare function that is used to determine the ordering of
25
- * options. See 'sortItems' for more control.
26
- */
27
- compareItems?(item1: ItemType, item2: ItemType, options: SharedOptions): number;
28
- /**
29
- * Provide a method that sorts all options in the control. Overriding this
30
- * prop means that you also have to handle the sort logic for selected versus
31
- * un-selected items. If you just want to control ordering, consider the
32
- * `compareItems` prop instead.
33
- *
34
- * The return value should be a number whose sign indicates the relative order
35
- * of the two elements: negative if a is less than b, positive if a is greater
36
- * than b, and zero if they are equal.
37
- *
38
- * sortItems :
39
- * (items: Array<Item>, {
40
- * selectedItems: Array<Item>,
41
- * itemToString: Item => string,
42
- * compareItems: (itemA: string, itemB: string, {
43
- * locale: string
44
- * }) => number,
45
- * locale: string,
46
- * }) => Array<Item>
47
- */
48
- sortItems?(items: ReadonlyArray<ItemType>, options: SortItemsOptions<ItemType>): ItemType[];
49
- }
50
13
  interface OnChangeData<ItemType> {
51
14
  selectedItems: ItemType[] | null;
52
15
  }
53
16
  export interface MultiSelectProps<ItemType> extends MultiSelectSortingProps<ItemType>, InternationalProps<'close.menu' | 'open.menu' | 'clear.all' | 'clear.selection'> {
17
+ /**
18
+ * **Experimental**: Will attempt to automatically align the floating
19
+ * element to avoid collisions with the viewport and being clipped by
20
+ * ancestor elements.
21
+ */
22
+ autoAlign?: boolean;
54
23
  className?: string;
55
24
  /**
56
25
  * Specify the text that should be read for screen readers that describes that all items are deleted