carbon-react 114.13.2 → 114.14.1

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 (41) hide show
  1. package/esm/components/link/link.component.js +7 -2
  2. package/esm/components/link/link.style.d.ts +1 -0
  3. package/esm/components/link/link.style.js +3 -4
  4. package/esm/components/select/filterable-select/filterable-select.component.js +15 -2
  5. package/esm/components/select/filterable-select/filterable-select.d.ts +7 -0
  6. package/esm/components/select/list-action-button/list-action-button.style.js +4 -1
  7. package/esm/components/select/multi-select/multi-select.component.js +15 -2
  8. package/esm/components/select/multi-select/multi-select.d.ts +7 -0
  9. package/esm/components/select/option/option.component.js +16 -5
  10. package/esm/components/select/option/option.style.js +4 -0
  11. package/esm/components/select/option-group-header/option-group-header.component.js +20 -8
  12. package/esm/components/select/option-group-header/option-group-header.style.js +3 -2
  13. package/esm/components/select/option-row/option-row.component.js +16 -5
  14. package/esm/components/select/option-row/option-row.style.js +4 -0
  15. package/esm/components/select/select-list/select-list-container.style.js +11 -1
  16. package/esm/components/select/select-list/select-list.component.js +136 -62
  17. package/esm/components/select/select-list/select-list.style.js +15 -18
  18. package/esm/components/select/simple-select/simple-select.component.js +15 -2
  19. package/esm/components/select/simple-select/simple-select.d.ts +7 -0
  20. package/lib/components/link/link.component.js +7 -1
  21. package/lib/components/link/link.style.d.ts +1 -0
  22. package/lib/components/link/link.style.js +3 -4
  23. package/lib/components/select/filterable-select/filterable-select.component.js +15 -2
  24. package/lib/components/select/filterable-select/filterable-select.d.ts +7 -0
  25. package/lib/components/select/list-action-button/list-action-button.style.js +4 -1
  26. package/lib/components/select/multi-select/multi-select.component.js +15 -2
  27. package/lib/components/select/multi-select/multi-select.d.ts +7 -0
  28. package/lib/components/select/option/option.component.js +16 -5
  29. package/lib/components/select/option/option.style.js +4 -0
  30. package/lib/components/select/option-group-header/option-group-header.component.js +20 -6
  31. package/lib/components/select/option-group-header/option-group-header.style.js +3 -2
  32. package/lib/components/select/option-row/option-row.component.js +16 -5
  33. package/lib/components/select/option-row/option-row.style.js +4 -0
  34. package/lib/components/select/select-list/select-list-container.style.js +11 -1
  35. package/lib/components/select/select-list/select-list.component.js +139 -63
  36. package/lib/components/select/select-list/select-list.style.js +15 -18
  37. package/lib/components/select/simple-select/simple-select.component.js +15 -2
  38. package/lib/components/select/simple-select/simple-select.d.ts +7 -0
  39. package/package.json +2 -1
  40. package/esm/components/select/select-list/update-list-scroll.js +0 -21
  41. package/lib/components/select/select-list/update-list-scroll.js +0 -28
@@ -3,11 +3,12 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
3
3
  import React, { useEffect, useState, useCallback, useLayoutEffect, useRef, useMemo } from "react";
4
4
  import PropTypes from "prop-types";
5
5
  import { flip, offset, size } from "@floating-ui/dom";
6
+ import { useVirtualizer } from "@tanstack/react-virtual";
7
+ import findLastIndex from "lodash/findLastIndex";
6
8
  import useScrollBlock from "../../../hooks/__internal__/useScrollBlock";
7
9
  import { StyledSelectList, StyledSelectLoaderContainer, StyledSelectListTable, StyledSelectListTableHeader, StyledSelectListTableBody } from "./select-list.style";
8
10
  import Popover from "../../../__internal__/popover";
9
11
  import OptionRow from "../option-row/option-row.component";
10
- import updateListScrollTop from "./update-list-scroll";
11
12
  import getNextChildByText from "../utils/get-next-child-by-text";
12
13
  import getNextIndexByKey from "../utils/get-next-index-by-key";
13
14
  import isNavigationKey from "../utils/is-navigation-key";
@@ -17,6 +18,11 @@ import Loader from "../../loader";
17
18
  import Option from "../option/option.component";
18
19
  import guid from "../../../__internal__/utils/helpers/guid";
19
20
  import SelectListContext from "../__internal__/select-list-context";
21
+ const TABLE_HEADER_HEIGHT = 48;
22
+ const SCROLL_OPTIONS = {
23
+ smoothScroll: false,
24
+ align: "end"
25
+ };
20
26
  const SelectList = /*#__PURE__*/React.forwardRef(({
21
27
  listMaxHeight = 180,
22
28
  listActionButton,
@@ -39,8 +45,12 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
39
45
  flipEnabled = true,
40
46
  isOpen,
41
47
  multiselectValues,
48
+ enableVirtualScroll,
49
+ virtualScrollOverscan = 5,
42
50
  ...listProps
43
51
  }, listContainerRef) => {
52
+ var _childIdsRef$current;
53
+
44
54
  const [currentOptionsListIndex, setCurrentOptionsListIndex] = useState(-1);
45
55
  const [scrollbarWidth, setScrollbarWidth] = useState(0);
46
56
  const lastFilter = useRef("");
@@ -51,6 +61,19 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
51
61
  blockScroll,
52
62
  allowScroll
53
63
  } = useScrollBlock();
64
+ const actionButtonHeight = useRef(0);
65
+ const overscan = enableVirtualScroll ? virtualScrollOverscan : React.Children.count(children);
66
+ const virtualizer = useVirtualizer({
67
+ count: React.Children.count(children),
68
+ getScrollElement: () => listContainerRef.current,
69
+ estimateSize: () => 40,
70
+ // value doesn't really seem to matter since we're dynamically measuring, but 40px is the height of a single-line option
71
+ overscan,
72
+ paddingStart: multiColumn ? TABLE_HEADER_HEIGHT : 0,
73
+ scrollPaddingEnd: actionButtonHeight.current
74
+ });
75
+ const items = virtualizer.getVirtualItems();
76
+ const listHeight = virtualizer.getTotalSize();
54
77
  useEffect(() => {
55
78
  if (isOpen) {
56
79
  blockScroll();
@@ -70,43 +93,67 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
70
93
  const anchorRef = useMemo(() => ({
71
94
  current: anchorElement
72
95
  }), [anchorElement]);
73
- const optionRefList = useMemo(() => React.Children.map(children, child => {
74
- if ((child === null || child === void 0 ? void 0 : child.type) === Option || (child === null || child === void 0 ? void 0 : child.type) === OptionRow) {
75
- return /*#__PURE__*/React.createRef();
76
- }
77
-
78
- return null;
79
- }).filter(child => child), [children]);
80
96
  const handleSelect = useCallback(optionData => {
81
97
  onSelect({ ...optionData,
82
98
  selectionType: "click"
83
99
  });
84
100
  }, [onSelect]);
85
- const childIds = useMemo(() => React.Children.map(children, () => guid()), [children]);
86
- const childrenWithListProps = useMemo(() => React.Children.map(children, (child, index) => {
87
- if (!child || child.type !== Option && child.type !== OptionRow) {
101
+ const childIdsRef = useRef(null); // childIds should be stable except when children are added or removed - can't use useMemo
102
+ // as that isn't absolutely guaranteed to never rerun when dependencies haven't changed.
103
+
104
+ const setChildIds = () => {
105
+ childIdsRef.current = React.Children.map(children, () => guid());
106
+ };
107
+
108
+ if (((_childIdsRef$current = childIdsRef.current) === null || _childIdsRef$current === void 0 ? void 0 : _childIdsRef$current.length) !== React.Children.count(children)) {
109
+ setChildIds();
110
+ }
111
+
112
+ const childIds = childIdsRef.current;
113
+ const childrenList = useMemo(() => React.Children.toArray(children), [children]);
114
+ const optionChildrenList = useMemo(() => childrenList.filter(child => child.type === Option || child.type === OptionRow), [childrenList]);
115
+ const {
116
+ measureElement
117
+ } = virtualizer;
118
+
119
+ const measureCallback = element => {
120
+ // need a guard to prevent crash with too many rerenders when closing the list
121
+ if (isOpen) {
122
+ measureElement(element);
123
+ }
124
+ };
125
+
126
+ const renderedChildren = items.map(item => {
127
+ const {
128
+ index,
129
+ start
130
+ } = item;
131
+ const child = childrenList[index];
132
+
133
+ if (!child) {
88
134
  return child;
89
135
  }
90
136
 
137
+ const optionChildIndex = optionChildrenList.indexOf(child);
138
+ const isOption = optionChildIndex > -1;
91
139
  const newProps = {
92
140
  index,
93
141
  id: childIds[index],
94
142
  onSelect: handleSelect,
95
- hidden: isLoading && React.Children.count(children) === 1,
96
- ref: optionRefList[index]
143
+ hidden: isLoading && childrenList.length === 1,
144
+ // these need to be inline styles rather than implemented in styled-components to avoid it generating thousands of classes
145
+ style: {
146
+ transform: `translateY(${start}px)`
147
+ },
148
+ "aria-setsize": isOption ? optionChildrenList.length : undefined,
149
+ "aria-posinset": isOption ? optionChildIndex + 1 : undefined,
150
+ // needed to dynamically compute the size
151
+ ref: measureCallback,
152
+ "data-index": index
97
153
  };
98
154
  return /*#__PURE__*/React.cloneElement(child, newProps);
99
- }), [children, handleSelect, isLoading, optionRefList, childIds]);
100
- const childrenList = useMemo(() => React.Children.toArray(childrenWithListProps), [childrenWithListProps]);
101
- const lastOptionIndex = useMemo(() => {
102
- let lastIndex = 0;
103
- childrenList.forEach((element, index) => {
104
- if (element.type === Option || element.type === OptionRow) {
105
- lastIndex = index;
106
- }
107
- });
108
- return lastIndex;
109
- }, [childrenList]);
155
+ });
156
+ const lastOptionIndex = findLastIndex(childrenList, child => child.type === Option || child.type === OptionRow);
110
157
  const getNextHighlightableItemIndex = useCallback((key, indexOfHighlighted) => {
111
158
  const lastIndex = lastOptionIndex;
112
159
  let nextIndex = getNextIndexByKey(key, indexOfHighlighted, lastIndex, isLoading);
@@ -137,16 +184,15 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
137
184
 
138
185
  const {
139
186
  text,
140
- value,
141
- id: itemId
187
+ value
142
188
  } = childrenList[nextIndex].props;
143
189
  onSelect({
144
190
  text,
145
191
  value,
146
192
  selectionType: "navigationKey",
147
- id: itemId
193
+ id: childIds[nextIndex]
148
194
  });
149
- }, [childrenList, currentOptionsListIndex, getIndexOfMatch, getNextHighlightableItemIndex, highlightedValue, onSelect]);
195
+ }, [childrenList, currentOptionsListIndex, getIndexOfMatch, getNextHighlightableItemIndex, highlightedValue, onSelect, childIds]);
150
196
  const handleActionButtonTab = useCallback((event, isActionButtonFocused) => {
151
197
  if (isActionButtonFocused) {
152
198
  onSelect({
@@ -186,12 +232,11 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
186
232
  }
187
233
 
188
234
  const {
189
- id: itemId,
190
235
  text,
191
236
  value
192
237
  } = currentOption.props;
193
238
  onSelect({
194
- id: itemId,
239
+ id: childIds[currentOptionsListIndex],
195
240
  text,
196
241
  value,
197
242
  selectionType: "enterKey"
@@ -200,9 +245,10 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
200
245
  focusOnAnchor();
201
246
  highlightNextItem(key);
202
247
  }
203
- }, [childrenList, listActionButton, handleActionButtonTab, onSelectListClose, currentOptionsListIndex, onSelect, highlightNextItem, focusOnAnchor, isOpen]);
248
+ }, [childrenList, listActionButton, handleActionButtonTab, onSelectListClose, currentOptionsListIndex, onSelect, highlightNextItem, focusOnAnchor, isOpen, childIds]);
204
249
  const handleListScroll = useCallback(event => {
205
250
  const element = event.target;
251
+ /* istanbul ignore else */
206
252
 
207
253
  if (onListScrollBottom && element.scrollHeight - element.scrollTop === element.clientHeight) {
208
254
  onListScrollBottom();
@@ -210,14 +256,14 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
210
256
  }, [onListScrollBottom]);
211
257
  useEffect(() => {
212
258
  const keyboardEvent = "keydown";
213
- const listElement = listRef.current;
259
+ const listElement = listContainerRef.current;
214
260
  window.addEventListener(keyboardEvent, handleGlobalKeydown);
215
261
  listElement.addEventListener("scroll", handleListScroll);
216
262
  return function cleanup() {
217
263
  window.removeEventListener(keyboardEvent, handleGlobalKeydown);
218
264
  listElement.removeEventListener("scroll", handleListScroll);
219
265
  };
220
- }, [handleGlobalKeydown, handleListScroll]);
266
+ }, [handleGlobalKeydown, handleListScroll, listContainerRef]);
221
267
  useEffect(() => {
222
268
  if (!filterText || filterText === lastFilter.current) {
223
269
  lastFilter.current = filterText;
@@ -233,10 +279,10 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
233
279
  }
234
280
 
235
281
  const indexOfMatch = getIndexOfMatch(match.props.value);
236
- updateListScrollTop(indexOfMatch, multiColumn ? tableRef.current : listRef.current, optionRefList);
282
+ virtualizer.scrollToIndex(indexOfMatch, SCROLL_OPTIONS);
237
283
  return indexOfMatch;
238
284
  });
239
- }, [childrenList, filterText, getIndexOfMatch, lastFilter, multiColumn, optionRefList]);
285
+ }, [childrenList, filterText, getIndexOfMatch, virtualizer]);
240
286
  useEffect(() => {
241
287
  if (!highlightedValue) {
242
288
  return;
@@ -244,13 +290,19 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
244
290
 
245
291
  const indexOfMatch = getIndexOfMatch(highlightedValue);
246
292
  setCurrentOptionsListIndex(indexOfMatch);
247
- updateListScrollTop(indexOfMatch, multiColumn ? tableRef.current : listRef.current, optionRefList);
248
- }, [childrenList, getIndexOfMatch, highlightedValue, multiColumn, optionRefList]);
293
+ virtualizer.scrollToIndex(indexOfMatch, SCROLL_OPTIONS); // TODO: is there a better way than calling handleListScroll manually?
294
+
295
+ handleListScroll({
296
+ target: listContainerRef.current
297
+ });
298
+ }, [getIndexOfMatch, highlightedValue, virtualizer, handleListScroll, listContainerRef]);
249
299
  useEffect(() => {
250
- if (isLoading && currentOptionsListIndex === lastOptionIndex) {
251
- listRef.current.scrollTop = listRef.current.scrollHeight;
300
+ if (isLoading && currentOptionsListIndex === lastOptionIndex && lastOptionIndex > -1) {
301
+ virtualizer.scrollToIndex(lastOptionIndex, { ...SCROLL_OPTIONS,
302
+ align: "start"
303
+ });
252
304
  }
253
- }, [children, currentOptionsListIndex, isLoading, lastOptionIndex]);
305
+ }, [children, currentOptionsListIndex, isLoading, lastOptionIndex, listContainerRef, virtualizer]);
254
306
  const popoverMiddleware = useMemo(() => [offset(3), size({
255
307
  apply({
256
308
  rects,
@@ -265,21 +317,37 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
265
317
  fallbackStrategy: "initialPlacement"
266
318
  })] : [])], [flipEnabled]);
267
319
 
268
- const loader = () => /*#__PURE__*/React.createElement(StyledSelectLoaderContainer, {
269
- key: "loader",
270
- as: multiColumn ? "div" : "li"
271
- }, /*#__PURE__*/React.createElement(Loader, {
272
- "data-role": loaderDataRole
273
- }));
320
+ const loader = () => {
321
+ return /*#__PURE__*/React.createElement(StyledSelectLoaderContainer, {
322
+ key: "loader"
323
+ }, /*#__PURE__*/React.createElement(Loader, {
324
+ "data-role": loaderDataRole
325
+ }));
326
+ };
327
+
328
+ let selectListContent = renderedChildren;
329
+ const listBoxProps = {
330
+ role: "listbox",
331
+ id,
332
+ "aria-labelledby": labelId,
333
+ "aria-multiselectable": multiselectValues ? true : undefined
334
+ };
335
+ useLayoutEffect(() => {
336
+ if (listActionButton && isOpen) {
337
+ var _listActionButtonRef$, _listActionButtonRef$2;
274
338
 
275
- let selectListContent = childrenWithListProps;
339
+ 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;
340
+ }
341
+ }, [listActionButton, isOpen]);
276
342
 
277
343
  if (multiColumn) {
278
344
  selectListContent = /*#__PURE__*/React.createElement(StyledSelectListTable, null, /*#__PURE__*/React.createElement(StyledSelectListTableHeader, {
279
345
  scrollbarWidth: scrollbarWidth
280
- }, tableHeader), /*#__PURE__*/React.createElement(StyledSelectListTableBody, {
281
- ref: tableRef
282
- }, childrenWithListProps));
346
+ }, tableHeader), /*#__PURE__*/React.createElement(StyledSelectListTableBody, _extends({}, listBoxProps, {
347
+ "aria-labelledby": labelId,
348
+ ref: tableRef,
349
+ listHeight: listHeight - TABLE_HEADER_HEIGHT
350
+ }), renderedChildren));
283
351
  }
284
352
 
285
353
  return /*#__PURE__*/React.createElement(SelectListContext.Provider, {
@@ -297,20 +365,17 @@ const SelectList = /*#__PURE__*/React.forwardRef(({
297
365
  animationFrame: true
298
366
  }, /*#__PURE__*/React.createElement(StyledSelectListContainer, _extends({
299
367
  "data-element": "select-list-wrapper",
300
- ref: listContainerRef
301
- }, listProps), /*#__PURE__*/React.createElement(StyledSelectList, {
302
- id: id,
368
+ ref: listContainerRef,
369
+ maxHeight: listMaxHeight + actionButtonHeight.current,
370
+ isLoading: isLoading
371
+ }, listProps), /*#__PURE__*/React.createElement(StyledSelectList, _extends({
303
372
  as: multiColumn ? "div" : "ul",
304
- "aria-labelledby": labelId,
305
- "data-element": "select-list",
306
- role: "listbox",
307
- "aria-multiselectable": multiselectValues ? true : undefined,
373
+ "data-element": "select-list"
374
+ }, multiColumn ? {} : listBoxProps, {
308
375
  ref: listRef,
309
376
  tabIndex: "-1",
310
- isLoading: isLoading,
311
- multiColumn: multiColumn,
312
- maxHeight: listMaxHeight
313
- }, selectListContent, isLoading && loader()), listActionButton && /*#__PURE__*/React.createElement(ListActionButton, {
377
+ listHeight: multiColumn ? undefined : listHeight
378
+ }), selectListContent), isLoading && loader(), listActionButton && /*#__PURE__*/React.createElement(ListActionButton, {
314
379
  ref: listActionButtonRef,
315
380
  listActionButton: listActionButton,
316
381
  onListAction: onListAction
@@ -380,6 +445,15 @@ SelectList.propTypes = {
380
445
  isOpen: PropTypes.bool,
381
446
 
382
447
  /** array of selected values, if rendered as part of a MultiSelect */
383
- multiselectValues: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.object)])
448
+ multiselectValues: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.object)]),
449
+
450
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
451
+ * DOM at all times, which may cause performance problems on very large lists */
452
+ enableVirtualScroll: PropTypes.bool,
453
+
454
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
455
+ * Higher values make for smoother scrolling but may impact performance.
456
+ * Only used if the `enableVirtualScroll` prop is set. */
457
+ virtualScrollOverscan: PropTypes.number
384
458
  };
385
459
  export default SelectList;
@@ -1,39 +1,29 @@
1
1
  import styled, { css } from "styled-components";
2
2
  const StyledSelectList = styled.ul`
3
3
  ${({
4
- isLoading,
5
- multiColumn
4
+ listHeight
6
5
  }) => css`
7
6
  box-sizing: border-box;
8
7
  display: flex;
9
8
  align-items: flex-start;
10
9
  flex-direction: column;
11
10
  list-style-type: none;
12
- max-height: ${({
13
- maxHeight
14
- }) => `${maxHeight}`}px;
15
11
  margin: 0;
16
12
  outline: none;
17
- overflow-x: hidden;
18
- overflow-y: ${multiColumn ? "hidden" : "auto"};
19
13
  padding: 0;
20
-
21
- ${isLoading && css`
22
- min-height: 150px;
23
- `}}
14
+ position: relative;
15
+ width: 100%;
16
+ ${listHeight === undefined ? "" : `height: ${listHeight}px;`}
24
17
  `}
25
18
  `;
26
- StyledSelectList.defaultProps = {
27
- maxHeight: "180px"
28
- };
29
- const StyledSelectLoaderContainer = styled.li`
19
+ const StyledSelectLoaderContainer = styled.div`
30
20
  display: flex;
31
21
  align-items: center;
32
22
  justify-content: center;
33
- flex-grow: 1;
34
23
  padding-top: 24px;
35
24
  padding-bottom: 24px;
36
25
  width: 100%;
26
+ flex-grow: 1;
37
27
  `;
38
28
  const StyledSelectListTable = styled.table`
39
29
  background-color: var(--colorsUtilityYang100);
@@ -43,6 +33,7 @@ const StyledSelectListTable = styled.table`
43
33
  min-width: 100%;
44
34
  white-space: nowrap;
45
35
  height: 180px;
36
+ overflow-y: auto;
46
37
 
47
38
  thead,
48
39
  tr {
@@ -54,6 +45,10 @@ const StyledSelectListTable = styled.table`
54
45
 
55
46
  const StyledSelectListTableHeader = styled.thead`
56
47
  border-bottom: 1px solid var(--colorsUtilityMajor050);
48
+ position: sticky;
49
+ top: 0;
50
+ left: 0;
51
+ z-index: 1;
57
52
 
58
53
  tr {
59
54
  width: ${({
@@ -89,9 +84,11 @@ const StyledSelectListTableHeader = styled.thead`
89
84
  `;
90
85
  const StyledSelectListTableBody = styled.tbody`
91
86
  display: block;
92
- overflow-y: auto;
93
87
  width: 100%;
94
88
  table-layout: fixed;
95
- max-height: 132px;
89
+ width: 100%;
90
+ height: ${({
91
+ listHeight
92
+ }) => `${listHeight}px`};
96
93
  `;
97
94
  export { StyledSelectList, StyledSelectLoaderContainer, StyledSelectListTable, StyledSelectListTableHeader, StyledSelectListTableBody };
@@ -43,6 +43,8 @@ const SimpleSelect = /*#__PURE__*/React.forwardRef(({
43
43
  listPlacement = "bottom",
44
44
  flipEnabled = true,
45
45
  inputRef,
46
+ enableVirtualScroll,
47
+ virtualScrollOverscan,
46
48
  ...props
47
49
  }, ref) => {
48
50
  const selectListId = useRef(guid());
@@ -350,7 +352,9 @@ const SimpleSelect = /*#__PURE__*/React.forwardRef(({
350
352
  loaderDataRole: "simple-select-list-loader",
351
353
  listPlacement: listPlacement,
352
354
  flipEnabled: flipEnabled,
353
- isOpen: isOpen
355
+ isOpen: isOpen,
356
+ enableVirtualScroll: enableVirtualScroll,
357
+ virtualScrollOverscan: virtualScrollOverscan
354
358
  }, children);
355
359
  return /*#__PURE__*/React.createElement(StyledSelect, _extends({
356
360
  transparent: transparent,
@@ -429,7 +433,16 @@ SimpleSelect.propTypes = {
429
433
  listPlacement: PropTypes.oneOf(["top", "bottom", "right", "left"]),
430
434
 
431
435
  /** Use the opposite list placement if the set placement does not fit */
432
- flipEnabled: PropTypes.bool
436
+ flipEnabled: PropTypes.bool,
437
+
438
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
439
+ * DOM at all times, which may cause performance problems on very large lists */
440
+ enableVirtualScroll: PropTypes.bool,
441
+
442
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
443
+ * Higher values make for smoother scrolling but may impact performance.
444
+ * Only used if the `enableVirtualScroll` prop is set. */
445
+ virtualScrollOverscan: PropTypes.number
433
446
  };
434
447
  SimpleSelect.defaultProps = {
435
448
  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(
@@ -11,6 +11,8 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
11
11
 
12
12
  var _icon = _interopRequireDefault(require("../icon"));
13
13
 
14
+ var _menu = _interopRequireDefault(require("../menu/menu.context"));
15
+
14
16
  var _events = _interopRequireDefault(require("../../__internal__/utils/helpers/events"));
15
17
 
16
18
  var _link = require("./link.style");
@@ -49,6 +51,9 @@ const Link = /*#__PURE__*/_react.default.forwardRef(({
49
51
  ...rest
50
52
  }, ref) => {
51
53
  const l = (0, _useLocale.default)();
54
+ const {
55
+ inMenu
56
+ } = (0, _react.useContext)(_menu.default);
52
57
 
53
58
  const handleOnKeyDown = ev => {
54
59
  if (onKeyDown) {
@@ -119,7 +124,8 @@ const Link = /*#__PURE__*/_react.default.forwardRef(({
119
124
  iconAlign: iconAlign,
120
125
  hasContent: Boolean(children),
121
126
  variant: variant,
122
- isDarkBackground: isDarkBackground
127
+ isDarkBackground: isDarkBackground,
128
+ isMenuItem: inMenu
123
129
  }, (0, _tags.default)("link", rest), isSkipLink && {
124
130
  "data-element": "skip-link"
125
131
  }), createLinkBasedOnType());
@@ -13,6 +13,7 @@ export interface StyledLinkProps {
13
13
  }
14
14
  interface PrivateStyledLinkProps {
15
15
  hasContent: boolean;
16
+ isMenuItem?: boolean;
16
17
  }
17
18
  declare const StyledLink: import("styled-components").StyledComponent<"span", any, StyledLinkProps & PrivateStyledLinkProps, never>;
18
19
  declare const StyledContent: import("styled-components").StyledComponent<"span", any, {}, never>;
@@ -61,7 +61,8 @@ const StyledLink = _styledComponents.default.span`
61
61
  hasContent,
62
62
  disabled,
63
63
  variant,
64
- isDarkBackground
64
+ isDarkBackground,
65
+ isMenuItem
65
66
  }) => {
66
67
  const colorMapKey = isDarkBackground ? "dark" : "light";
67
68
  const {
@@ -70,8 +71,6 @@ const StyledLink = _styledComponents.default.span`
70
71
  disabledColor
71
72
  } = colorMap[colorMapKey](variant);
72
73
  return (0, _styledComponents.css)`
73
- display: inline-block;
74
-
75
74
  ${isSkipLink && (0, _styledComponents.css)`
76
75
  a {
77
76
  position: absolute;
@@ -142,7 +141,7 @@ const StyledLink = _styledComponents.default.span`
142
141
  a,
143
142
  button {
144
143
  text-decoration: underline;
145
- display: inline-block;
144
+ ${isMenuItem && "display: inline-block;"}
146
145
 
147
146
  ${_icon.default} {
148
147
  display: inline-block;
@@ -73,6 +73,8 @@ const FilterableSelect = /*#__PURE__*/_react.default.forwardRef(({
73
73
  listPlacement = "bottom",
74
74
  flipEnabled = true,
75
75
  inputRef,
76
+ enableVirtualScroll,
77
+ virtualScrollOverscan,
76
78
  ...textboxProps
77
79
  }, ref) => {
78
80
  const [activeDescendantId, setActiveDescendantId] = (0, _react.useState)();
@@ -455,7 +457,9 @@ const FilterableSelect = /*#__PURE__*/_react.default.forwardRef(({
455
457
  loaderDataRole: "filterable-select-list-loader",
456
458
  listPlacement: listPlacement,
457
459
  flipEnabled: flipEnabled,
458
- isOpen: isOpen
460
+ isOpen: isOpen,
461
+ enableVirtualScroll: enableVirtualScroll,
462
+ virtualScrollOverscan: virtualScrollOverscan
459
463
  }, children);
460
464
 
461
465
  return /*#__PURE__*/_react.default.createElement(_select.default, _extends({
@@ -543,7 +547,16 @@ FilterableSelect.propTypes = { ..._selectTextbox.formInputPropTypes,
543
547
  listPlacement: _propTypes.default.oneOf(["top", "bottom", "right", "left"]),
544
548
 
545
549
  /** Use the opposite list placement if the set placement does not fit */
546
- flipEnabled: _propTypes.default.bool
550
+ flipEnabled: _propTypes.default.bool,
551
+
552
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
553
+ * DOM at all times, which may cause performance problems on very large lists */
554
+ enableVirtualScroll: _propTypes.default.bool,
555
+
556
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
557
+ * Higher values make for smoother scrolling but may impact performance.
558
+ * Only used if the `enableVirtualScroll` prop is set. */
559
+ virtualScrollOverscan: _propTypes.default.number
547
560
  };
548
561
  FilterableSelect.defaultProps = {
549
562
  "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(
@@ -16,13 +16,16 @@ const StyledListActionButtonWrapper = _styledComponents.default.div`
16
16
  padding-bottom: var(--spacing100);
17
17
  border-top: 1px solid var(--colorsUtilityDisabled600);
18
18
  box-shadow: 0 0px 0 0 rgba(0, 0, 0, 0), 0 -8px 8px 0 rgba(0, 0, 0, 0.03);
19
+ width: 100%;
20
+ position: sticky;
21
+ bottom: 0;
22
+ background-color: inherit;
19
23
 
20
24
  ${_button.default} {
21
25
  border: none;
22
26
  justify-content: left;
23
27
  padding-left: var(--spacing200);
24
28
  padding-right: var(--spacing200);
25
- width: 100%;
26
29
  }
27
30
  `;
28
31
  var _default = StyledListActionButtonWrapper;
@@ -76,6 +76,8 @@ const MultiSelect = /*#__PURE__*/_react.default.forwardRef(({
76
76
  flipEnabled = true,
77
77
  wrapPillText = true,
78
78
  inputRef,
79
+ enableVirtualScroll,
80
+ virtualScrollOverscan,
79
81
  ...textboxProps
80
82
  }, ref) => {
81
83
  const [activeDescendantId, setActiveDescendantId] = (0, _react.useState)();
@@ -468,7 +470,9 @@ const MultiSelect = /*#__PURE__*/_react.default.forwardRef(({
468
470
  flipEnabled: flipEnabled,
469
471
  loaderDataRole: "multi-select-list-loader",
470
472
  multiselectValues: actualValue,
471
- isOpen: isOpen
473
+ isOpen: isOpen,
474
+ enableVirtualScroll: enableVirtualScroll,
475
+ virtualScrollOverscan: virtualScrollOverscan
472
476
  }, children);
473
477
 
474
478
  return /*#__PURE__*/_react.default.createElement(_multiSelect.StyledSelectMultiSelect, _extends({
@@ -555,7 +559,16 @@ MultiSelect.propTypes = { ..._selectTextbox.formInputPropTypes,
555
559
  flipEnabled: _propTypes.default.bool,
556
560
 
557
561
  /** Wraps the pill text when it would overflow the input width */
558
- wrapPillText: _propTypes.default.bool
562
+ wrapPillText: _propTypes.default.bool,
563
+
564
+ /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
565
+ * DOM at all times, which may cause performance problems on very large lists */
566
+ enableVirtualScroll: _propTypes.default.bool,
567
+
568
+ /** The number of options to render into the DOM at once, either side of the currently-visible ones.
569
+ * Higher values make for smoother scrolling but may impact performance.
570
+ * Only used if the `enableVirtualScroll` prop is set. */
571
+ virtualScrollOverscan: _propTypes.default.number
559
572
  };
560
573
  MultiSelect.defaultProps = {
561
574
  "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(