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,6 +11,10 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
11
11
 
12
12
  var _dom = require("@floating-ui/dom");
13
13
 
14
+ var _reactVirtual = require("@tanstack/react-virtual");
15
+
16
+ var _findLastIndex = _interopRequireDefault(require("lodash/findLastIndex"));
17
+
14
18
  var _useScrollBlock = _interopRequireDefault(require("../../../hooks/__internal__/useScrollBlock"));
15
19
 
16
20
  var _selectList = require("./select-list.style");
@@ -19,8 +23,6 @@ var _popover = _interopRequireDefault(require("../../../__internal__/popover"));
19
23
 
20
24
  var _optionRow = _interopRequireDefault(require("../option-row/option-row.component"));
21
25
 
22
- var _updateListScroll = _interopRequireDefault(require("./update-list-scroll"));
23
-
24
26
  var _getNextChildByText = _interopRequireDefault(require("../utils/get-next-child-by-text"));
25
27
 
26
28
  var _getNextIndexByKey = _interopRequireDefault(require("../utils/get-next-index-by-key"));
@@ -47,6 +49,12 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj;
47
49
 
48
50
  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); }
49
51
 
52
+ const TABLE_HEADER_HEIGHT = 48;
53
+ const SCROLL_OPTIONS = {
54
+ smoothScroll: false,
55
+ align: "end"
56
+ };
57
+
50
58
  const SelectList = /*#__PURE__*/_react.default.forwardRef(({
51
59
  listMaxHeight = 180,
52
60
  listActionButton,
@@ -69,8 +77,12 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
69
77
  flipEnabled = true,
70
78
  isOpen,
71
79
  multiselectValues,
80
+ enableVirtualScroll,
81
+ virtualScrollOverscan = 5,
72
82
  ...listProps
73
83
  }, listContainerRef) => {
84
+ var _childIdsRef$current;
85
+
74
86
  const [currentOptionsListIndex, setCurrentOptionsListIndex] = (0, _react.useState)(-1);
75
87
  const [scrollbarWidth, setScrollbarWidth] = (0, _react.useState)(0);
76
88
  const lastFilter = (0, _react.useRef)("");
@@ -81,6 +93,19 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
81
93
  blockScroll,
82
94
  allowScroll
83
95
  } = (0, _useScrollBlock.default)();
96
+ const actionButtonHeight = (0, _react.useRef)(0);
97
+ const overscan = enableVirtualScroll ? virtualScrollOverscan : _react.default.Children.count(children);
98
+ const virtualizer = (0, _reactVirtual.useVirtualizer)({
99
+ count: _react.default.Children.count(children),
100
+ getScrollElement: () => listContainerRef.current,
101
+ estimateSize: () => 40,
102
+ // value doesn't really seem to matter since we're dynamically measuring, but 40px is the height of a single-line option
103
+ overscan,
104
+ paddingStart: multiColumn ? TABLE_HEADER_HEIGHT : 0,
105
+ scrollPaddingEnd: actionButtonHeight.current
106
+ });
107
+ const items = virtualizer.getVirtualItems();
108
+ const listHeight = virtualizer.getTotalSize();
84
109
  (0, _react.useEffect)(() => {
85
110
  if (isOpen) {
86
111
  blockScroll();
@@ -100,43 +125,67 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
100
125
  const anchorRef = (0, _react.useMemo)(() => ({
101
126
  current: anchorElement
102
127
  }), [anchorElement]);
103
- const optionRefList = (0, _react.useMemo)(() => _react.default.Children.map(children, child => {
104
- if ((child === null || child === void 0 ? void 0 : child.type) === _option.default || (child === null || child === void 0 ? void 0 : child.type) === _optionRow.default) {
105
- return /*#__PURE__*/_react.default.createRef();
106
- }
107
-
108
- return null;
109
- }).filter(child => child), [children]);
110
128
  const handleSelect = (0, _react.useCallback)(optionData => {
111
129
  onSelect({ ...optionData,
112
130
  selectionType: "click"
113
131
  });
114
132
  }, [onSelect]);
115
- const childIds = (0, _react.useMemo)(() => _react.default.Children.map(children, () => (0, _guid.default)()), [children]);
116
- const childrenWithListProps = (0, _react.useMemo)(() => _react.default.Children.map(children, (child, index) => {
117
- if (!child || child.type !== _option.default && child.type !== _optionRow.default) {
133
+ const childIdsRef = (0, _react.useRef)(null); // childIds should be stable except when children are added or removed - can't use useMemo
134
+ // as that isn't absolutely guaranteed to never rerun when dependencies haven't changed.
135
+
136
+ const setChildIds = () => {
137
+ childIdsRef.current = _react.default.Children.map(children, () => (0, _guid.default)());
138
+ };
139
+
140
+ if (((_childIdsRef$current = childIdsRef.current) === null || _childIdsRef$current === void 0 ? void 0 : _childIdsRef$current.length) !== _react.default.Children.count(children)) {
141
+ setChildIds();
142
+ }
143
+
144
+ const childIds = childIdsRef.current;
145
+ const childrenList = (0, _react.useMemo)(() => _react.default.Children.toArray(children), [children]);
146
+ const optionChildrenList = (0, _react.useMemo)(() => childrenList.filter(child => child.type === _option.default || child.type === _optionRow.default), [childrenList]);
147
+ const {
148
+ measureElement
149
+ } = virtualizer;
150
+
151
+ const measureCallback = element => {
152
+ // need a guard to prevent crash with too many rerenders when closing the list
153
+ if (isOpen) {
154
+ measureElement(element);
155
+ }
156
+ };
157
+
158
+ const renderedChildren = items.map(item => {
159
+ const {
160
+ index,
161
+ start
162
+ } = item;
163
+ const child = childrenList[index];
164
+
165
+ if (!child) {
118
166
  return child;
119
167
  }
120
168
 
169
+ const optionChildIndex = optionChildrenList.indexOf(child);
170
+ const isOption = optionChildIndex > -1;
121
171
  const newProps = {
122
172
  index,
123
173
  id: childIds[index],
124
174
  onSelect: handleSelect,
125
- hidden: isLoading && _react.default.Children.count(children) === 1,
126
- ref: optionRefList[index]
175
+ hidden: isLoading && childrenList.length === 1,
176
+ // these need to be inline styles rather than implemented in styled-components to avoid it generating thousands of classes
177
+ style: {
178
+ transform: `translateY(${start}px)`
179
+ },
180
+ "aria-setsize": isOption ? optionChildrenList.length : undefined,
181
+ "aria-posinset": isOption ? optionChildIndex + 1 : undefined,
182
+ // needed to dynamically compute the size
183
+ ref: measureCallback,
184
+ "data-index": index
127
185
  };
128
186
  return /*#__PURE__*/_react.default.cloneElement(child, newProps);
129
- }), [children, handleSelect, isLoading, optionRefList, childIds]);
130
- const childrenList = (0, _react.useMemo)(() => _react.default.Children.toArray(childrenWithListProps), [childrenWithListProps]);
131
- const lastOptionIndex = (0, _react.useMemo)(() => {
132
- let lastIndex = 0;
133
- childrenList.forEach((element, index) => {
134
- if (element.type === _option.default || element.type === _optionRow.default) {
135
- lastIndex = index;
136
- }
137
- });
138
- return lastIndex;
139
- }, [childrenList]);
187
+ });
188
+ const lastOptionIndex = (0, _findLastIndex.default)(childrenList, child => child.type === _option.default || child.type === _optionRow.default);
140
189
  const getNextHighlightableItemIndex = (0, _react.useCallback)((key, indexOfHighlighted) => {
141
190
  const lastIndex = lastOptionIndex;
142
191
  let nextIndex = (0, _getNextIndexByKey.default)(key, indexOfHighlighted, lastIndex, isLoading);
@@ -167,16 +216,15 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
167
216
 
168
217
  const {
169
218
  text,
170
- value,
171
- id: itemId
219
+ value
172
220
  } = childrenList[nextIndex].props;
173
221
  onSelect({
174
222
  text,
175
223
  value,
176
224
  selectionType: "navigationKey",
177
- id: itemId
225
+ id: childIds[nextIndex]
178
226
  });
179
- }, [childrenList, currentOptionsListIndex, getIndexOfMatch, getNextHighlightableItemIndex, highlightedValue, onSelect]);
227
+ }, [childrenList, currentOptionsListIndex, getIndexOfMatch, getNextHighlightableItemIndex, highlightedValue, onSelect, childIds]);
180
228
  const handleActionButtonTab = (0, _react.useCallback)((event, isActionButtonFocused) => {
181
229
  if (isActionButtonFocused) {
182
230
  onSelect({
@@ -216,12 +264,11 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
216
264
  }
217
265
 
218
266
  const {
219
- id: itemId,
220
267
  text,
221
268
  value
222
269
  } = currentOption.props;
223
270
  onSelect({
224
- id: itemId,
271
+ id: childIds[currentOptionsListIndex],
225
272
  text,
226
273
  value,
227
274
  selectionType: "enterKey"
@@ -230,9 +277,10 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
230
277
  focusOnAnchor();
231
278
  highlightNextItem(key);
232
279
  }
233
- }, [childrenList, listActionButton, handleActionButtonTab, onSelectListClose, currentOptionsListIndex, onSelect, highlightNextItem, focusOnAnchor, isOpen]);
280
+ }, [childrenList, listActionButton, handleActionButtonTab, onSelectListClose, currentOptionsListIndex, onSelect, highlightNextItem, focusOnAnchor, isOpen, childIds]);
234
281
  const handleListScroll = (0, _react.useCallback)(event => {
235
282
  const element = event.target;
283
+ /* istanbul ignore else */
236
284
 
237
285
  if (onListScrollBottom && element.scrollHeight - element.scrollTop === element.clientHeight) {
238
286
  onListScrollBottom();
@@ -240,14 +288,14 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
240
288
  }, [onListScrollBottom]);
241
289
  (0, _react.useEffect)(() => {
242
290
  const keyboardEvent = "keydown";
243
- const listElement = listRef.current;
291
+ const listElement = listContainerRef.current;
244
292
  window.addEventListener(keyboardEvent, handleGlobalKeydown);
245
293
  listElement.addEventListener("scroll", handleListScroll);
246
294
  return function cleanup() {
247
295
  window.removeEventListener(keyboardEvent, handleGlobalKeydown);
248
296
  listElement.removeEventListener("scroll", handleListScroll);
249
297
  };
250
- }, [handleGlobalKeydown, handleListScroll]);
298
+ }, [handleGlobalKeydown, handleListScroll, listContainerRef]);
251
299
  (0, _react.useEffect)(() => {
252
300
  if (!filterText || filterText === lastFilter.current) {
253
301
  lastFilter.current = filterText;
@@ -263,10 +311,10 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
263
311
  }
264
312
 
265
313
  const indexOfMatch = getIndexOfMatch(match.props.value);
266
- (0, _updateListScroll.default)(indexOfMatch, multiColumn ? tableRef.current : listRef.current, optionRefList);
314
+ virtualizer.scrollToIndex(indexOfMatch, SCROLL_OPTIONS);
267
315
  return indexOfMatch;
268
316
  });
269
- }, [childrenList, filterText, getIndexOfMatch, lastFilter, multiColumn, optionRefList]);
317
+ }, [childrenList, filterText, getIndexOfMatch, virtualizer]);
270
318
  (0, _react.useEffect)(() => {
271
319
  if (!highlightedValue) {
272
320
  return;
@@ -274,13 +322,19 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
274
322
 
275
323
  const indexOfMatch = getIndexOfMatch(highlightedValue);
276
324
  setCurrentOptionsListIndex(indexOfMatch);
277
- (0, _updateListScroll.default)(indexOfMatch, multiColumn ? tableRef.current : listRef.current, optionRefList);
278
- }, [childrenList, getIndexOfMatch, highlightedValue, multiColumn, optionRefList]);
325
+ virtualizer.scrollToIndex(indexOfMatch, SCROLL_OPTIONS); // TODO: is there a better way than calling handleListScroll manually?
326
+
327
+ handleListScroll({
328
+ target: listContainerRef.current
329
+ });
330
+ }, [getIndexOfMatch, highlightedValue, virtualizer, handleListScroll, listContainerRef]);
279
331
  (0, _react.useEffect)(() => {
280
- if (isLoading && currentOptionsListIndex === lastOptionIndex) {
281
- listRef.current.scrollTop = listRef.current.scrollHeight;
332
+ if (isLoading && currentOptionsListIndex === lastOptionIndex && lastOptionIndex > -1) {
333
+ virtualizer.scrollToIndex(lastOptionIndex, { ...SCROLL_OPTIONS,
334
+ align: "start"
335
+ });
282
336
  }
283
- }, [children, currentOptionsListIndex, isLoading, lastOptionIndex]);
337
+ }, [children, currentOptionsListIndex, isLoading, lastOptionIndex, listContainerRef, virtualizer]);
284
338
  const popoverMiddleware = (0, _react.useMemo)(() => [(0, _dom.offset)(3), (0, _dom.size)({
285
339
  apply({
286
340
  rects,
@@ -295,21 +349,37 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
295
349
  fallbackStrategy: "initialPlacement"
296
350
  })] : [])], [flipEnabled]);
297
351
 
298
- const loader = () => /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectLoaderContainer, {
299
- key: "loader",
300
- as: multiColumn ? "div" : "li"
301
- }, /*#__PURE__*/_react.default.createElement(_loader.default, {
302
- "data-role": loaderDataRole
303
- }));
352
+ const loader = () => {
353
+ return /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectLoaderContainer, {
354
+ key: "loader"
355
+ }, /*#__PURE__*/_react.default.createElement(_loader.default, {
356
+ "data-role": loaderDataRole
357
+ }));
358
+ };
304
359
 
305
- let selectListContent = childrenWithListProps;
360
+ let selectListContent = renderedChildren;
361
+ const listBoxProps = {
362
+ role: "listbox",
363
+ id,
364
+ "aria-labelledby": labelId,
365
+ "aria-multiselectable": multiselectValues ? true : undefined
366
+ };
367
+ (0, _react.useLayoutEffect)(() => {
368
+ if (listActionButton && isOpen) {
369
+ var _listActionButtonRef$, _listActionButtonRef$2;
370
+
371
+ actionButtonHeight.current = ((_listActionButtonRef$ = listActionButtonRef.current) === null || _listActionButtonRef$ === void 0 ? void 0 : (_listActionButtonRef$2 = _listActionButtonRef$.parentElement) === null || _listActionButtonRef$2 === void 0 ? void 0 : _listActionButtonRef$2.offsetHeight) || 0;
372
+ }
373
+ }, [listActionButton, isOpen]);
306
374
 
307
375
  if (multiColumn) {
308
376
  selectListContent = /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectListTable, null, /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectListTableHeader, {
309
377
  scrollbarWidth: scrollbarWidth
310
- }, tableHeader), /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectListTableBody, {
311
- ref: tableRef
312
- }, childrenWithListProps));
378
+ }, tableHeader), /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectListTableBody, _extends({}, listBoxProps, {
379
+ "aria-labelledby": labelId,
380
+ ref: tableRef,
381
+ listHeight: listHeight - TABLE_HEADER_HEIGHT
382
+ }), renderedChildren));
313
383
  }
314
384
 
315
385
  return /*#__PURE__*/_react.default.createElement(_selectListContext.default.Provider, {
@@ -327,20 +397,17 @@ const SelectList = /*#__PURE__*/_react.default.forwardRef(({
327
397
  animationFrame: true
328
398
  }, /*#__PURE__*/_react.default.createElement(_selectListContainer.default, _extends({
329
399
  "data-element": "select-list-wrapper",
330
- ref: listContainerRef
331
- }, listProps), /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectList, {
332
- id: id,
400
+ ref: listContainerRef,
401
+ maxHeight: listMaxHeight + actionButtonHeight.current,
402
+ isLoading: isLoading
403
+ }, listProps), /*#__PURE__*/_react.default.createElement(_selectList.StyledSelectList, _extends({
333
404
  as: multiColumn ? "div" : "ul",
334
- "aria-labelledby": labelId,
335
- "data-element": "select-list",
336
- role: "listbox",
337
- "aria-multiselectable": multiselectValues ? true : undefined,
405
+ "data-element": "select-list"
406
+ }, multiColumn ? {} : listBoxProps, {
338
407
  ref: listRef,
339
408
  tabIndex: "-1",
340
- isLoading: isLoading,
341
- multiColumn: multiColumn,
342
- maxHeight: listMaxHeight
343
- }, selectListContent, isLoading && loader()), listActionButton && /*#__PURE__*/_react.default.createElement(_listActionButton.default, {
409
+ listHeight: multiColumn ? undefined : listHeight
410
+ }), selectListContent), isLoading && loader(), listActionButton && /*#__PURE__*/_react.default.createElement(_listActionButton.default, {
344
411
  ref: listActionButtonRef,
345
412
  listActionButton: listActionButton,
346
413
  onListAction: onListAction
@@ -411,7 +478,16 @@ SelectList.propTypes = {
411
478
  isOpen: _propTypes.default.bool,
412
479
 
413
480
  /** array of selected values, if rendered as part of a MultiSelect */
414
- multiselectValues: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.arrayOf(_propTypes.default.object)])
481
+ multiselectValues: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.arrayOf(_propTypes.default.object)]),
482
+
483
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
484
+ * DOM at all times, which may cause performance problems on very large lists */
485
+ enableVirtualScroll: _propTypes.default.bool,
486
+
487
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
488
+ * Higher values make for smoother scrolling but may impact performance.
489
+ * Only used if the `enableVirtualScroll` prop is set. */
490
+ virtualScrollOverscan: _propTypes.default.number
415
491
  };
416
492
  var _default = SelectList;
417
493
  exports.default = _default;
@@ -13,40 +13,30 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj;
13
13
 
14
14
  const StyledSelectList = _styledComponents.default.ul`
15
15
  ${({
16
- isLoading,
17
- multiColumn
16
+ listHeight
18
17
  }) => (0, _styledComponents.css)`
19
18
  box-sizing: border-box;
20
19
  display: flex;
21
20
  align-items: flex-start;
22
21
  flex-direction: column;
23
22
  list-style-type: none;
24
- max-height: ${({
25
- maxHeight
26
- }) => `${maxHeight}`}px;
27
23
  margin: 0;
28
24
  outline: none;
29
- overflow-x: hidden;
30
- overflow-y: ${multiColumn ? "hidden" : "auto"};
31
25
  padding: 0;
32
-
33
- ${isLoading && (0, _styledComponents.css)`
34
- min-height: 150px;
35
- `}}
26
+ position: relative;
27
+ width: 100%;
28
+ ${listHeight === undefined ? "" : `height: ${listHeight}px;`}
36
29
  `}
37
30
  `;
38
31
  exports.StyledSelectList = StyledSelectList;
39
- StyledSelectList.defaultProps = {
40
- maxHeight: "180px"
41
- };
42
- const StyledSelectLoaderContainer = _styledComponents.default.li`
32
+ const StyledSelectLoaderContainer = _styledComponents.default.div`
43
33
  display: flex;
44
34
  align-items: center;
45
35
  justify-content: center;
46
- flex-grow: 1;
47
36
  padding-top: 24px;
48
37
  padding-bottom: 24px;
49
38
  width: 100%;
39
+ flex-grow: 1;
50
40
  `;
51
41
  exports.StyledSelectLoaderContainer = StyledSelectLoaderContainer;
52
42
  const StyledSelectListTable = _styledComponents.default.table`
@@ -57,6 +47,7 @@ const StyledSelectListTable = _styledComponents.default.table`
57
47
  min-width: 100%;
58
48
  white-space: nowrap;
59
49
  height: 180px;
50
+ overflow-y: auto;
60
51
 
61
52
  thead,
62
53
  tr {
@@ -69,6 +60,10 @@ const StyledSelectListTable = _styledComponents.default.table`
69
60
  exports.StyledSelectListTable = StyledSelectListTable;
70
61
  const StyledSelectListTableHeader = _styledComponents.default.thead`
71
62
  border-bottom: 1px solid var(--colorsUtilityMajor050);
63
+ position: sticky;
64
+ top: 0;
65
+ left: 0;
66
+ z-index: 1;
72
67
 
73
68
  tr {
74
69
  width: ${({
@@ -105,9 +100,11 @@ const StyledSelectListTableHeader = _styledComponents.default.thead`
105
100
  exports.StyledSelectListTableHeader = StyledSelectListTableHeader;
106
101
  const StyledSelectListTableBody = _styledComponents.default.tbody`
107
102
  display: block;
108
- overflow-y: auto;
109
103
  width: 100%;
110
104
  table-layout: fixed;
111
- max-height: 132px;
105
+ width: 100%;
106
+ height: ${({
107
+ listHeight
108
+ }) => `${listHeight}px`};
112
109
  `;
113
110
  exports.StyledSelectListTableBody = StyledSelectListTableBody;
@@ -70,6 +70,8 @@ const SimpleSelect = /*#__PURE__*/_react.default.forwardRef(({
70
70
  listPlacement = "bottom",
71
71
  flipEnabled = true,
72
72
  inputRef,
73
+ enableVirtualScroll,
74
+ virtualScrollOverscan,
73
75
  ...props
74
76
  }, ref) => {
75
77
  const selectListId = (0, _react.useRef)((0, _guid.default)());
@@ -378,7 +380,9 @@ const SimpleSelect = /*#__PURE__*/_react.default.forwardRef(({
378
380
  loaderDataRole: "simple-select-list-loader",
379
381
  listPlacement: listPlacement,
380
382
  flipEnabled: flipEnabled,
381
- isOpen: isOpen
383
+ isOpen: isOpen,
384
+ enableVirtualScroll: enableVirtualScroll,
385
+ virtualScrollOverscan: virtualScrollOverscan
382
386
  }, children);
383
387
 
384
388
  return /*#__PURE__*/_react.default.createElement(_select.default, _extends({
@@ -459,7 +463,16 @@ SimpleSelect.propTypes = {
459
463
  listPlacement: _propTypes.default.oneOf(["top", "bottom", "right", "left"]),
460
464
 
461
465
  /** Use the opposite list placement if the set placement does not fit */
462
- flipEnabled: _propTypes.default.bool
466
+ flipEnabled: _propTypes.default.bool,
467
+
468
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
469
+ * DOM at all times, which may cause performance problems on very large lists */
470
+ enableVirtualScroll: _propTypes.default.bool,
471
+
472
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
473
+ * Higher values make for smoother scrolling but may impact performance.
474
+ * Only used if the `enableVirtualScroll` prop is set. */
475
+ virtualScrollOverscan: _propTypes.default.number
463
476
  };
464
477
  SimpleSelect.defaultProps = {
465
478
  disablePortal: false,
@@ -44,6 +44,13 @@ export interface SimpleSelectProps
44
44
  listPlacement?: Side;
45
45
  /** Use the opposite list placement if the set placement does not fit */
46
46
  flipEnabled?: boolean;
47
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
48
+ * DOM at all times, which may cause performance problems on very large lists */
49
+ enableVirtualScroll?: boolean;
50
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
51
+ * Higher values make for smoother scrolling but may impact performance.
52
+ * Only used if the `enableVirtualScroll` prop is set. */
53
+ virtualScrollOverscan?: number;
47
54
  }
48
55
 
49
56
  declare function SimpleSelect(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carbon-react",
3
- "version": "114.13.1",
3
+ "version": "114.14.0",
4
4
  "description": "A library of reusable React components for easily building user interfaces.",
5
5
  "files": [
6
6
  "lib",
@@ -166,6 +166,7 @@
166
166
  "@floating-ui/react-dom": "^1.0.1",
167
167
  "@octokit/rest": "^18.12.0",
168
168
  "@styled-system/prop-types": "^5.1.5",
169
+ "@tanstack/react-virtual": "^3.0.0-beta.39",
169
170
  "@types/styled-system": "^5.1.11",
170
171
  "chalk": "^4.1.1",
171
172
  "ci-info": "^3.3.2",
@@ -1,21 +0,0 @@
1
- export default function updateListScrollTop(indexOfCurrent, list, options) {
2
- if (!list || !options[indexOfCurrent] || options[indexOfCurrent].current === null) {
3
- list.scrollTop = 0;
4
- return;
5
- }
6
-
7
- let newPosition = 0;
8
- const {
9
- offsetHeight: listHeight
10
- } = list;
11
- const {
12
- offsetTop: itemTop,
13
- offsetHeight: currentItemHeight
14
- } = options[indexOfCurrent].current;
15
-
16
- if (itemTop + currentItemHeight > listHeight) {
17
- newPosition = itemTop + currentItemHeight - listHeight;
18
- }
19
-
20
- list.scrollTop = newPosition;
21
- }
@@ -1,28 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = updateListScrollTop;
7
-
8
- function updateListScrollTop(indexOfCurrent, list, options) {
9
- if (!list || !options[indexOfCurrent] || options[indexOfCurrent].current === null) {
10
- list.scrollTop = 0;
11
- return;
12
- }
13
-
14
- let newPosition = 0;
15
- const {
16
- offsetHeight: listHeight
17
- } = list;
18
- const {
19
- offsetTop: itemTop,
20
- offsetHeight: currentItemHeight
21
- } = options[indexOfCurrent].current;
22
-
23
- if (itemTop + currentItemHeight > listHeight) {
24
- newPosition = itemTop + currentItemHeight - listHeight;
25
- }
26
-
27
- list.scrollTop = newPosition;
28
- }