carbon-react 114.13.1 → 114.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.
Files changed (66) hide show
  1. package/esm/__internal__/input-icon-toggle/input-icon-toggle.component.js +2 -1
  2. package/esm/components/link/link.component.d.ts +2 -0
  3. package/esm/components/link/link.component.js +7 -1
  4. package/esm/components/menu/__internal__/keyboard-navigation/index.d.ts +4 -2
  5. package/esm/components/menu/__internal__/keyboard-navigation/index.js +16 -15
  6. package/esm/components/menu/__internal__/locators.d.ts +6 -0
  7. package/esm/components/menu/__internal__/locators.js +6 -0
  8. package/esm/components/menu/__internal__/submenu/submenu.component.js +109 -108
  9. package/esm/components/menu/menu-full-screen/menu-full-screen.component.js +1 -2
  10. package/esm/components/menu/menu-item/index.js +0 -1
  11. package/esm/components/menu/menu-item/menu-item.component.js +77 -51
  12. package/esm/components/menu/menu-item/menu-item.d.ts +7 -3
  13. package/esm/components/menu/menu.component.js +33 -37
  14. package/esm/components/menu/menu.context.d.ts +2 -2
  15. package/esm/components/menu/menu.context.js +2 -2
  16. package/esm/components/menu/scrollable-block/scrollable-block.component.js +6 -24
  17. package/esm/components/select/filterable-select/filterable-select.component.js +15 -2
  18. package/esm/components/select/filterable-select/filterable-select.d.ts +7 -0
  19. package/esm/components/select/list-action-button/list-action-button.style.js +4 -1
  20. package/esm/components/select/multi-select/multi-select.component.js +15 -2
  21. package/esm/components/select/multi-select/multi-select.d.ts +7 -0
  22. package/esm/components/select/option/option.component.js +16 -5
  23. package/esm/components/select/option/option.style.js +4 -0
  24. package/esm/components/select/option-group-header/option-group-header.component.js +20 -8
  25. package/esm/components/select/option-group-header/option-group-header.style.js +3 -2
  26. package/esm/components/select/option-row/option-row.component.js +16 -5
  27. package/esm/components/select/option-row/option-row.style.js +4 -0
  28. package/esm/components/select/select-list/select-list-container.style.js +11 -1
  29. package/esm/components/select/select-list/select-list.component.js +136 -62
  30. package/esm/components/select/select-list/select-list.style.js +15 -18
  31. package/esm/components/select/simple-select/simple-select.component.js +15 -2
  32. package/esm/components/select/simple-select/simple-select.d.ts +7 -0
  33. package/lib/__internal__/input-icon-toggle/input-icon-toggle.component.js +2 -1
  34. package/lib/components/link/link.component.d.ts +2 -0
  35. package/lib/components/link/link.component.js +7 -1
  36. package/lib/components/menu/__internal__/keyboard-navigation/index.d.ts +4 -2
  37. package/lib/components/menu/__internal__/keyboard-navigation/index.js +16 -15
  38. package/lib/components/menu/__internal__/locators.d.ts +6 -0
  39. package/lib/components/menu/__internal__/locators.js +18 -0
  40. package/lib/components/menu/__internal__/submenu/submenu.component.js +111 -113
  41. package/lib/components/menu/menu-full-screen/menu-full-screen.component.js +1 -2
  42. package/lib/components/menu/menu-item/menu-item.component.js +76 -52
  43. package/lib/components/menu/menu-item/menu-item.d.ts +7 -3
  44. package/lib/components/menu/menu.component.js +33 -37
  45. package/lib/components/menu/menu.context.d.ts +2 -2
  46. package/lib/components/menu/menu.context.js +2 -2
  47. package/lib/components/menu/scrollable-block/scrollable-block.component.js +6 -25
  48. package/lib/components/select/filterable-select/filterable-select.component.js +15 -2
  49. package/lib/components/select/filterable-select/filterable-select.d.ts +7 -0
  50. package/lib/components/select/list-action-button/list-action-button.style.js +4 -1
  51. package/lib/components/select/multi-select/multi-select.component.js +15 -2
  52. package/lib/components/select/multi-select/multi-select.d.ts +7 -0
  53. package/lib/components/select/option/option.component.js +16 -5
  54. package/lib/components/select/option/option.style.js +4 -0
  55. package/lib/components/select/option-group-header/option-group-header.component.js +20 -6
  56. package/lib/components/select/option-group-header/option-group-header.style.js +3 -2
  57. package/lib/components/select/option-row/option-row.component.js +16 -5
  58. package/lib/components/select/option-row/option-row.style.js +4 -0
  59. package/lib/components/select/select-list/select-list-container.style.js +11 -1
  60. package/lib/components/select/select-list/select-list.component.js +139 -63
  61. package/lib/components/select/select-list/select-list.style.js +15 -18
  62. package/lib/components/select/simple-select/simple-select.component.js +15 -2
  63. package/lib/components/select/simple-select/simple-select.d.ts +7 -0
  64. package/package.json +2 -1
  65. package/esm/components/select/select-list/update-list-scroll.js +0 -21
  66. package/lib/components/select/select-list/update-list-scroll.js +0 -28
@@ -11,7 +11,7 @@ import MenuContext from "../menu.context";
11
11
  import Submenu from "../__internal__/submenu/submenu.component";
12
12
  import SubmenuContext from "../__internal__/submenu/submenu.context";
13
13
  import { StyledMenuItem } from "../menu.style";
14
- import Search from "../../search";
14
+ import guid from "../../../__internal__/utils/helpers/guid";
15
15
 
16
16
  const MenuItem = ({
17
17
  submenu,
@@ -33,41 +33,70 @@ const MenuItem = ({
33
33
  onSubmenuClose,
34
34
  overrideColor,
35
35
  rel,
36
- isFocused,
37
36
  ...rest
38
37
  }) => {
39
- const menuContext = useContext(MenuContext);
38
+ var _ref$current, _ref$current2;
39
+
40
+ const {
41
+ inFullscreenView,
42
+ registerItem,
43
+ unregisterItem,
44
+ focusId,
45
+ menuType,
46
+ openSubmenuId
47
+ } = useContext(MenuContext);
48
+ const menuItemId = useRef(guid());
40
49
  const submenuContext = useContext(SubmenuContext);
41
- const ref = useRef(null);
42
- const focusFromMenu = isFocused;
43
- const focusFromSubmenu = submenuContext.isFocused;
44
- const isChildSearch = useRef(false);
45
- const childRef = useRef();
46
50
  const {
47
- inFullscreenView
48
- } = menuContext;
49
- const childrenItems = React.Children.map(children, child => {
50
- if ((child === null || child === void 0 ? void 0 : child.type) === Search) {
51
- isChildSearch.current = true;
51
+ registerItem: registerSubmenuItem,
52
+ unregisterItem: unregisterSubmenuItem,
53
+ submenuFocusId,
54
+ updateFocusId: updateSubmenuFocusId,
55
+ handleKeyDown: handleSubmenuKeyDown,
56
+ shiftTabPressed
57
+ } = submenuContext;
58
+ const ref = useRef(null);
59
+ const focusFromMenu = focusId === menuItemId.current;
60
+ const focusFromSubmenu = submenuFocusId ? submenuFocusId === menuItemId.current : undefined;
61
+ const inputRef = useRef(null);
62
+ const inputIcon = useRef(null);
63
+ inputIcon.current = (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.querySelector("[data-element='input-icon-toggle']");
64
+ inputRef.current = (_ref$current2 = ref.current) === null || _ref$current2 === void 0 ? void 0 : _ref$current2.querySelector("[data-element='input']");
65
+ const focusRef = inputRef.current ? inputRef : ref;
66
+ useEffect(() => {
67
+ const id = menuItemId.current;
68
+
69
+ if (registerSubmenuItem) {
70
+ registerSubmenuItem(id);
71
+ } else if (registerItem) {
72
+ registerItem(id);
52
73
  }
53
74
 
54
- return child;
55
- });
56
- const focusRef = isChildSearch.current ? childRef : ref;
75
+ return () => {
76
+ if (unregisterSubmenuItem) {
77
+ unregisterSubmenuItem(id);
78
+ } else if (unregisterItem) {
79
+ unregisterItem(id);
80
+ }
81
+ };
82
+ }, [registerSubmenuItem, registerItem, unregisterSubmenuItem, unregisterItem]);
57
83
  useEffect(() => {
58
- if (focusFromSubmenu === undefined && focusFromMenu) {
84
+ var _inputIcon$current;
85
+
86
+ if (!openSubmenuId && focusFromSubmenu === undefined && focusFromMenu) {
59
87
  focusRef.current.focus();
60
- } else if (focusFromSubmenu) {
88
+ } else if (focusFromSubmenu && !(shiftTabPressed && ((_inputIcon$current = inputIcon.current) === null || _inputIcon$current === void 0 ? void 0 : _inputIcon$current.getAttribute("tabindex")) === "0")) {
61
89
  focusRef.current.focus();
62
90
  }
63
- }, [focusFromMenu, focusFromSubmenu, focusRef]);
91
+ }, [openSubmenuId, focusFromMenu, focusFromSubmenu, inputIcon, shiftTabPressed, focusRef]);
64
92
  const updateFocusOnClick = useCallback(() => {
65
- /* istanbul ignore else */
66
- if (submenuContext.updateFocusIndex) {
67
- submenuContext.updateFocusIndex(submenuContext.itemIndex);
93
+ if (updateSubmenuFocusId) {
94
+ updateSubmenuFocusId(menuItemId.current);
68
95
  }
69
- }, [submenuContext]);
96
+ }, [updateSubmenuFocusId]);
70
97
  const handleKeyDown = useCallback(event => {
98
+ var _inputIcon$current2, _inputRef$current;
99
+
71
100
  if (onKeyDown) {
72
101
  onKeyDown(event);
73
102
  }
@@ -76,16 +105,16 @@ const MenuItem = ({
76
105
  ref.current.focus();
77
106
  }
78
107
 
79
- if (submenuContext.handleKeyDown !== undefined) {
80
- var _focusRef$current;
108
+ const shouldFocusIcon = ((_inputIcon$current2 = inputIcon.current) === null || _inputIcon$current2 === void 0 ? void 0 : _inputIcon$current2.getAttribute("tabindex")) === "0" && document.activeElement === inputRef.current && ((_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.value); // let natural tab order move focus if input icon is tabbable
81
109
 
82
- if (!(isChildSearch.current && document.activeElement === focusRef.current && (_focusRef$current = focusRef.current) !== null && _focusRef$current !== void 0 && _focusRef$current.value)) {
83
- submenuContext.handleKeyDown(event);
84
- }
85
- } else {
86
- menuContext.handleKeyDown(event);
110
+ if (Events.isTabKey(event) && (!Events.isShiftKey(event) && shouldFocusIcon || Events.isShiftKey(event) && document.activeElement === inputIcon.current)) {
111
+ return;
87
112
  }
88
- }, [focusRef, menuContext, onKeyDown, submenuContext]);
113
+
114
+ if (handleSubmenuKeyDown) {
115
+ handleSubmenuKeyDown(event);
116
+ }
117
+ }, [onKeyDown, handleSubmenuKeyDown, inputIcon]);
89
118
  const classes = useMemo(() => classNames({
90
119
  "carbon-menu-item--has-link": href || onClick
91
120
  }), [href, onClick]);
@@ -94,7 +123,7 @@ const MenuItem = ({
94
123
  href,
95
124
  target,
96
125
  rel,
97
- onClick: onClick || (isChildSearch.current ? updateFocusOnClick : undefined),
126
+ onClick: onClick || (inputRef.current ? updateFocusOnClick : undefined),
98
127
  icon,
99
128
  selected,
100
129
  variant,
@@ -102,11 +131,8 @@ const MenuItem = ({
102
131
  overrideColor,
103
132
  ref
104
133
  };
105
- const clonedChildren = isChildSearch.current ? childrenItems.map(child => /*#__PURE__*/React.cloneElement(child, {
106
- ref: childRef
107
- })) : children;
108
134
 
109
- const getTitle = title => maxWidth && typeof title === "string" ? title : "";
135
+ const getTitle = title => maxWidth && typeof title === "string" ? title : undefined;
110
136
 
111
137
  const itemMaxWidth = !inFullscreenView ? maxWidth : undefined;
112
138
  const asPassiveItem = !(onClick || href);
@@ -114,13 +140,14 @@ const MenuItem = ({
114
140
  if (submenu) {
115
141
  return /*#__PURE__*/React.createElement(StyledMenuItem, _extends({
116
142
  "data-component": "menu-item",
117
- menuType: menuContext.menuType,
143
+ menuType: menuType,
118
144
  display: "inline-block",
119
145
  title: getTitle(submenu),
120
146
  maxWidth: itemMaxWidth,
121
147
  onClick: updateFocusOnClick
122
148
  }, rest, {
123
- inFullscreenView: inFullscreenView
149
+ inFullscreenView: inFullscreenView,
150
+ id: menuItemId.current
124
151
  }), /*#__PURE__*/React.createElement(Submenu, _extends({}, typeof submenu !== "boolean" && {
125
152
  title: submenu
126
153
  }, {
@@ -132,29 +159,31 @@ const MenuItem = ({
132
159
  ariaLabel: ariaLabel,
133
160
  onSubmenuOpen: onSubmenuOpen,
134
161
  onSubmenuClose: onSubmenuClose
135
- }, elementProps, rest), childrenItems));
162
+ }, elementProps, rest), children));
136
163
  }
137
164
 
138
165
  return /*#__PURE__*/React.createElement(StyledMenuItem, _extends({
139
166
  "data-component": "menu-item",
140
- menuType: menuContext.menuType,
141
- inSubmenu: submenuContext.handleKeyDown !== undefined,
167
+ menuType: menuType,
168
+ inSubmenu: !!handleSubmenuKeyDown,
142
169
  display: "inline-block",
143
170
  title: getTitle(children),
144
171
  maxWidth: itemMaxWidth
145
172
  }, rest, {
146
173
  inFullscreenView: inFullscreenView && !Object.keys(submenuContext).length,
147
- menuOpen: menuOpen
174
+ menuOpen: menuOpen,
175
+ id: menuItemId.current
148
176
  }), /*#__PURE__*/React.createElement(StyledMenuItemWrapper, _extends({
149
- as: isChildSearch.current ? "div" : Link,
150
- isSearch: isChildSearch.current,
151
- menuType: menuContext.menuType
177
+ as: inputRef.current ? "div" : Link,
178
+ isSearch: inputRef.current,
179
+ menuType: menuType
152
180
  }, elementProps, {
153
181
  ariaLabel: ariaLabel,
154
182
  maxWidth: maxWidth,
155
183
  inFullscreenView: inFullscreenView,
156
- asPassiveItem: asPassiveItem
157
- }), clonedChildren));
184
+ asPassiveItem: asPassiveItem,
185
+ placeholderTabIndex: asPassiveItem
186
+ }), children));
158
187
  };
159
188
 
160
189
  MenuItem.propTypes = {
@@ -243,10 +272,7 @@ MenuItem.propTypes = {
243
272
  overrideColor: PropTypes.bool,
244
273
 
245
274
  /** @ignore @private */
246
- isFocused: PropTypes.bool,
247
-
248
- /** @ignore @private */
249
- indexInMenu: PropTypes.number
275
+ "data-component": PropTypes.string
250
276
  };
251
277
  MenuItem.displayName = "MenuItem";
252
278
  export default MenuItem;
@@ -31,10 +31,14 @@ export interface MenuItemBaseProps extends LayoutProps, FlexboxProps {
31
31
  onSubmenuOpen?: () => void;
32
32
  /** Callback triggered when submenu closes. Only valid with submenu prop */
33
33
  onSubmenuClose?: () => void;
34
- /** @ignore @private
35
- private prop, used inside ScrollableBlock to ensure the MenuItem's color variant overrides the CSS
36
- for other MenuItems inside the block */
34
+ /**
35
+ @ignore @private
36
+ private prop, used inside ScrollableBlock to ensure the MenuItem's color variant overrides the CSS
37
+ for other MenuItems inside the block
38
+ */
37
39
  overrideColor?: boolean;
40
+ /** @private @ignore */
41
+ "data-component"?: string;
38
42
  }
39
43
 
40
44
  export interface MenuWithChildren extends MenuItemBaseProps {
@@ -1,64 +1,60 @@
1
1
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
2
 
3
- import React, { useEffect, useState, useCallback, useRef } from "react";
3
+ import React, { useCallback, useState, useRef } from "react";
4
4
  import PropTypes from "prop-types";
5
5
  import propTypes from "@styled-system/prop-types";
6
6
  import { StyledMenuWrapper } from "./menu.style";
7
- import { menuKeyboardNavigation } from "./__internal__/keyboard-navigation";
8
- import Events from "../../__internal__/utils/helpers/events";
9
7
  import MenuContext from "./menu.context";
8
+ import { menuKeyboardNavigation } from "./__internal__/keyboard-navigation";
9
+ import { MENU_ITEM_CHILDREN_LOCATOR } from "./__internal__/locators";
10
10
 
11
11
  const Menu = ({
12
12
  menuType = "light",
13
13
  children,
14
14
  ...rest
15
15
  }) => {
16
- const [focusedItemIndex, setFocusedItemIndex] = useState(undefined);
17
- const [openSubmenuIndex, setOpenSubmenuIndex] = useState(null);
16
+ const [openSubmenuId, setOpenSubmenuId] = useState(null);
18
17
  const ref = useRef();
19
- const handleKeyDown = useCallback(event => {
20
- const newIndex = menuKeyboardNavigation(event, React.Children.toArray(children));
21
- setFocusedItemIndex(newIndex);
22
- }, [children]);
23
- const onClickOutside = useCallback(event => {
24
- // Reset the state of the menu when clicking elsewhere
25
- if (!Events.composedPath(event).includes(ref.current)) {
26
- setFocusedItemIndex(undefined);
27
- document.removeEventListener("click", onClickOutside);
28
- }
18
+ const [focusId, setFocusId] = useState(undefined);
19
+ const [itemIds, setItemIds] = useState([]);
20
+ const registerItem = useCallback(id => {
21
+ setItemIds(prevState => {
22
+ return [...prevState, id];
23
+ });
29
24
  }, []);
30
- useEffect(() => {
31
- document.addEventListener("click", onClickOutside);
32
- return function cleanup() {
33
- document.removeEventListener("click", onClickOutside);
34
- };
35
- });
25
+ const unregisterItem = useCallback(id => {
26
+ setItemIds(prevState => {
27
+ return prevState.filter(itemId => itemId !== id);
28
+ });
29
+ }, []);
30
+
31
+ const handleKeyDown = event => {
32
+ /* istanbul ignore else */
33
+ if (ref.current) {
34
+ const focusableItems = Array.from(ref.current.querySelectorAll(MENU_ITEM_CHILDREN_LOCATOR));
35
+ const newIndex = menuKeyboardNavigation(event, focusableItems);
36
+ setFocusId(itemIds[newIndex]);
37
+ }
38
+ };
39
+
36
40
  return /*#__PURE__*/React.createElement(StyledMenuWrapper, _extends({
37
41
  "data-component": "menu",
38
42
  menuType: menuType
39
43
  }, rest, {
40
44
  ref: ref,
41
- role: "list"
45
+ role: "list",
46
+ onKeyDown: handleKeyDown
42
47
  }), /*#__PURE__*/React.createElement(MenuContext.Provider, {
43
48
  value: {
44
49
  menuType,
45
- handleKeyDown,
46
50
  inMenu: true,
47
- openSubmenuIndex,
48
- setOpenSubmenuIndex
51
+ openSubmenuId,
52
+ setOpenSubmenuId,
53
+ focusId,
54
+ registerItem,
55
+ unregisterItem
49
56
  }
50
- }, React.Children.map(children, (child, index) => {
51
- const isFocused = focusedItemIndex === index;
52
-
53
- if ( /*#__PURE__*/React.isValidElement(child) && child.type.displayName === "MenuItem" && child.props.submenu) {
54
- return /*#__PURE__*/React.cloneElement(child, {
55
- isFocused,
56
- indexInMenu: index
57
- });
58
- }
59
-
60
- return child;
61
- })));
57
+ }, children));
62
58
  };
63
59
 
64
60
  Menu.propTypes = {
@@ -1,8 +1,8 @@
1
1
  declare var _default: React.Context<{
2
2
  menuType: string;
3
3
  inMenu: boolean;
4
- openSubmenuIndex: null;
5
- setOpenSubmenuIndex: () => void;
4
+ openSubmenuId: null;
5
+ setOpenSubmenuId: () => void;
6
6
  }>;
7
7
  export default _default;
8
8
  import React from "react";
@@ -2,8 +2,8 @@ import React from "react";
2
2
  export default /*#__PURE__*/React.createContext({
3
3
  menuType: "light",
4
4
  inMenu: false,
5
- openSubmenuIndex: null,
6
- setOpenSubmenuIndex:
5
+ openSubmenuId: null,
6
+ setOpenSubmenuId:
7
7
  /* istanbul ignore next */
8
8
  () => {}
9
9
  });
@@ -4,7 +4,6 @@ import React, { useContext } from "react";
4
4
  import PropTypes from "prop-types";
5
5
  import MenuContext from "../menu.context";
6
6
  import MenuItem from "../menu-item";
7
- import SubmenuContext from "../__internal__/submenu/submenu.context";
8
7
  import StyledScrollableBlock from "./scrollable-block.style";
9
8
  import Box from "../../box";
10
9
 
@@ -16,13 +15,9 @@ const ScrollableBlock = ({
16
15
  parentVariant,
17
16
  ...rest
18
17
  }) => {
19
- const menuContext = useContext(MenuContext);
20
- const submenuContext = useContext(SubmenuContext);
21
18
  const {
22
- blockIndex,
23
- focusIndex,
24
- handleKeyDown
25
- } = submenuContext;
19
+ menuType
20
+ } = useContext(MenuContext);
26
21
  const scrollVariants = {
27
22
  light: "light",
28
23
  dark: "dark",
@@ -31,35 +26,22 @@ const ScrollableBlock = ({
31
26
  };
32
27
  return /*#__PURE__*/React.createElement(StyledScrollableBlock, _extends({
33
28
  "data-component": "submenu-scrollable-block",
34
- menuType: menuContext.menuType,
29
+ menuType: menuType,
35
30
  variant: variant
36
31
  }, rest), parent && /*#__PURE__*/React.createElement(MenuItem, {
32
+ "data-component": "scrollable-block-parent",
37
33
  overrideColor: true,
38
34
  variant: parentVariant,
39
35
  as: "div",
40
36
  href: "#"
41
37
  }, parent), /*#__PURE__*/React.createElement(Box, {
42
38
  overflowY: "scroll",
43
- scrollVariant: scrollVariants[menuContext.menuType],
39
+ scrollVariant: scrollVariants[menuType],
44
40
  height: height,
45
41
  p: 0,
46
42
  as: "ul",
47
43
  role: "list"
48
- }, React.Children.map(children, (child, index) => {
49
- let isFocused = false;
50
- const blockItemFocused = focusIndex >= blockIndex;
51
-
52
- if (blockItemFocused) {
53
- isFocused = focusIndex - blockIndex === index;
54
- }
55
-
56
- return /*#__PURE__*/React.createElement(SubmenuContext.Provider, {
57
- value: {
58
- isFocused,
59
- handleKeyDown
60
- }
61
- }, child);
62
- })));
44
+ }, children));
63
45
  };
64
46
 
65
47
  ScrollableBlock.propTypes = {
@@ -47,6 +47,8 @@ const FilterableSelect = /*#__PURE__*/React.forwardRef(({
47
47
  listPlacement = "bottom",
48
48
  flipEnabled = true,
49
49
  inputRef,
50
+ enableVirtualScroll,
51
+ virtualScrollOverscan,
50
52
  ...textboxProps
51
53
  }, ref) => {
52
54
  const [activeDescendantId, setActiveDescendantId] = useState();
@@ -428,7 +430,9 @@ const FilterableSelect = /*#__PURE__*/React.forwardRef(({
428
430
  loaderDataRole: "filterable-select-list-loader",
429
431
  listPlacement: listPlacement,
430
432
  flipEnabled: flipEnabled,
431
- isOpen: isOpen
433
+ isOpen: isOpen,
434
+ enableVirtualScroll: enableVirtualScroll,
435
+ virtualScrollOverscan: virtualScrollOverscan
432
436
  }, children);
433
437
  return /*#__PURE__*/React.createElement(StyledSelect, _extends({
434
438
  hasTextCursor: true,
@@ -514,7 +518,16 @@ FilterableSelect.propTypes = { ...formInputPropTypes,
514
518
  listPlacement: PropTypes.oneOf(["top", "bottom", "right", "left"]),
515
519
 
516
520
  /** Use the opposite list placement if the set placement does not fit */
517
- flipEnabled: PropTypes.bool
521
+ flipEnabled: PropTypes.bool,
522
+
523
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
524
+ * DOM at all times, which may cause performance problems on very large lists */
525
+ enableVirtualScroll: PropTypes.bool,
526
+
527
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
528
+ * Higher values make for smoother scrolling but may impact performance.
529
+ * Only used if the `enableVirtualScroll` prop is set. */
530
+ virtualScrollOverscan: PropTypes.number
518
531
  };
519
532
  FilterableSelect.defaultProps = {
520
533
  "data-component": "filterable-select"
@@ -51,6 +51,13 @@ export interface FilterableSelectProps
51
51
  listPlacement?: Side;
52
52
  /** Use the opposite list placement if the set placement does not fit */
53
53
  flipEnabled?: boolean;
54
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
55
+ * DOM at all times, which may cause performance problems on very large lists */
56
+ enableVirtualScroll?: boolean;
57
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
58
+ * Higher values make for smoother scrolling but may impact performance.
59
+ * Only used if the `enableVirtualScroll` prop is set. */
60
+ virtualScrollOverscan?: number;
54
61
  }
55
62
 
56
63
  declare function FilterableSelect(
@@ -5,13 +5,16 @@ const StyledListActionButtonWrapper = styled.div`
5
5
  padding-bottom: var(--spacing100);
6
6
  border-top: 1px solid var(--colorsUtilityDisabled600);
7
7
  box-shadow: 0 0px 0 0 rgba(0, 0, 0, 0), 0 -8px 8px 0 rgba(0, 0, 0, 0.03);
8
+ width: 100%;
9
+ position: sticky;
10
+ bottom: 0;
11
+ background-color: inherit;
8
12
 
9
13
  ${StyledButton} {
10
14
  border: none;
11
15
  justify-content: left;
12
16
  padding-left: var(--spacing200);
13
17
  padding-right: var(--spacing200);
14
- width: 100%;
15
18
  }
16
19
  `;
17
20
  export default StyledListActionButtonWrapper;
@@ -48,6 +48,8 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
48
48
  flipEnabled = true,
49
49
  wrapPillText = true,
50
50
  inputRef,
51
+ enableVirtualScroll,
52
+ virtualScrollOverscan,
51
53
  ...textboxProps
52
54
  }, ref) => {
53
55
  const [activeDescendantId, setActiveDescendantId] = useState();
@@ -438,7 +440,9 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
438
440
  flipEnabled: flipEnabled,
439
441
  loaderDataRole: "multi-select-list-loader",
440
442
  multiselectValues: actualValue,
441
- isOpen: isOpen
443
+ isOpen: isOpen,
444
+ enableVirtualScroll: enableVirtualScroll,
445
+ virtualScrollOverscan: virtualScrollOverscan
442
446
  }, children);
443
447
  return /*#__PURE__*/React.createElement(StyledSelectMultiSelect, _extends({
444
448
  disabled: disabled,
@@ -523,7 +527,16 @@ MultiSelect.propTypes = { ...formInputPropTypes,
523
527
  flipEnabled: PropTypes.bool,
524
528
 
525
529
  /** Wraps the pill text when it would overflow the input width */
526
- wrapPillText: PropTypes.bool
530
+ wrapPillText: PropTypes.bool,
531
+
532
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
533
+ * DOM at all times, which may cause performance problems on very large lists */
534
+ enableVirtualScroll: PropTypes.bool,
535
+
536
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
537
+ * Higher values make for smoother scrolling but may impact performance.
538
+ * Only used if the `enableVirtualScroll` prop is set. */
539
+ virtualScrollOverscan: PropTypes.number
527
540
  };
528
541
  MultiSelect.defaultProps = {
529
542
  "data-component": "multiselect"
@@ -48,6 +48,13 @@ export interface MultiSelectProps
48
48
  flipEnabled?: boolean;
49
49
  /** Wraps the pill text when it would overflow the input width */
50
50
  wrapPillText?: boolean;
51
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
52
+ * DOM at all times, which may cause performance problems on very large lists */
53
+ enableVirtualScroll?: boolean;
54
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
55
+ * Higher values make for smoother scrolling but may impact performance.
56
+ * Only used if the `enableVirtualScroll` prop is set. */
57
+ virtualScrollOverscan?: number;
51
58
  }
52
59
 
53
60
  declare function MultiSelect(
@@ -1,3 +1,5 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
1
3
  import React, { useContext } from "react";
2
4
  import PropTypes from "prop-types";
3
5
  import StyledOption from "./option.style";
@@ -10,7 +12,9 @@ const Option = /*#__PURE__*/React.forwardRef(({
10
12
  id,
11
13
  index,
12
14
  hidden,
13
- onClick
15
+ onClick,
16
+ style,
17
+ ...rest
14
18
  }, ref) => {
15
19
  const selectListContext = useContext(SelectListContext);
16
20
  let isSelected = selectListContext.currentOptionsListIndex === index;
@@ -38,7 +42,7 @@ const Option = /*#__PURE__*/React.forwardRef(({
38
42
  }
39
43
  }
40
44
 
41
- return /*#__PURE__*/React.createElement(StyledOption, {
45
+ return /*#__PURE__*/React.createElement(StyledOption, _extends({
42
46
  id: id,
43
47
  ref: ref,
44
48
  "aria-selected": isSelected,
@@ -46,8 +50,9 @@ const Option = /*#__PURE__*/React.forwardRef(({
46
50
  onClick: handleClick,
47
51
  isHighlighted: selectListContext.currentOptionsListIndex === index,
48
52
  role: "option",
49
- hidden: hidden
50
- }, children || text);
53
+ hidden: hidden,
54
+ style: style
55
+ }, rest), children || text);
51
56
  });
52
57
  Option.propTypes = {
53
58
  /** The option's visible text, displayed within Textbox of Select, and used for filtering */
@@ -96,6 +101,12 @@ Option.propTypes = {
96
101
 
97
102
  /** MultiSelect only - fill Pill background with color */
98
103
  // eslint-disable-next-line react/no-unused-prop-types
99
- fill: PropTypes.bool
104
+ fill: PropTypes.bool,
105
+
106
+ /**
107
+ * @private
108
+ * @ignore
109
+ * object containing CSS styles to be passed to the underlying list-item */
110
+ style: PropTypes.object
100
111
  };
101
112
  export default Option;
@@ -7,6 +7,10 @@ const StyledOption = styled.li`
7
7
  padding: 12px 16px;
8
8
  width: 100%;
9
9
  user-select: none;
10
+ position: absolute;
11
+ top: 0;
12
+ left: 0;
13
+ width: 100%;
10
14
 
11
15
  ${({
12
16
  isHighlighted
@@ -1,17 +1,23 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
1
3
  import React from "react";
2
4
  import PropTypes from "prop-types";
3
5
  import StyledOptionGroupHeader from "./option-group-header.style";
4
6
  import Icon from "../../icon";
5
-
6
- const OptionGroupHeader = ({
7
+ const OptionGroupHeader = /*#__PURE__*/React.forwardRef(({
7
8
  label,
8
- icon
9
- }) => {
10
- return /*#__PURE__*/React.createElement(StyledOptionGroupHeader, null, icon && /*#__PURE__*/React.createElement(Icon, {
9
+ icon,
10
+ style,
11
+ ...rest
12
+ }, ref) => {
13
+ return /*#__PURE__*/React.createElement(StyledOptionGroupHeader, _extends({
14
+ style: style
15
+ }, rest, {
16
+ ref: ref
17
+ }), icon && /*#__PURE__*/React.createElement(Icon, {
11
18
  type: icon
12
19
  }), /*#__PURE__*/React.createElement("h4", null, label));
13
- };
14
-
20
+ });
15
21
  OptionGroupHeader.propTypes = {
16
22
  /** Heading text */
17
23
  label: PropTypes.string.isRequired,
@@ -21,6 +27,12 @@ OptionGroupHeader.propTypes = {
21
27
  *
22
28
  * Any valid Carbon icon name
23
29
  */
24
- icon: PropTypes.string
30
+ icon: PropTypes.string,
31
+
32
+ /**
33
+ * @private
34
+ * @ignore
35
+ * object containing CSS styles to be passed to the underlying DOM element */
36
+ style: PropTypes.object
25
37
  };
26
38
  export default OptionGroupHeader;
@@ -2,9 +2,10 @@ import styled from "styled-components";
2
2
  import StyledIcon from "../../icon/icon.style";
3
3
  const StyledOptionGroupHeader = styled.div`
4
4
  box-sizing: border-box;
5
- height: 26px;
6
- margin: 16px 0 0;
5
+ position: absolute;
6
+ height: 40px;
7
7
  padding-left: 16px;
8
+ padding-top: 16px;
8
9
  display: flex;
9
10
  align-items: center;
10
11
  width: 100%;