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
@@ -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 StyledOptionRow from "./option-row.style";
@@ -9,7 +11,9 @@ const OptionRow = /*#__PURE__*/React.forwardRef(({
9
11
  onSelect,
10
12
  value,
11
13
  index,
12
- hidden
14
+ hidden,
15
+ style,
16
+ ...rest
13
17
  }, ref) => {
14
18
  const handleClick = () => {
15
19
  onSelect({
@@ -26,7 +30,7 @@ const OptionRow = /*#__PURE__*/React.forwardRef(({
26
30
  isSelected = selectListContext.multiselectValues.includes(value);
27
31
  }
28
32
 
29
- return /*#__PURE__*/React.createElement(StyledOptionRow, {
33
+ return /*#__PURE__*/React.createElement(StyledOptionRow, _extends({
30
34
  id: id,
31
35
  ref: ref,
32
36
  "aria-selected": isSelected,
@@ -34,8 +38,9 @@ const OptionRow = /*#__PURE__*/React.forwardRef(({
34
38
  onClick: handleClick,
35
39
  isHighlighted: selectListContext.currentOptionsListIndex === index,
36
40
  role: "option",
37
- hidden: hidden
38
- }, children);
41
+ hidden: hidden,
42
+ style: style
43
+ }, rest), children);
39
44
  });
40
45
  OptionRow.propTypes = {
41
46
  /** The option's visible text, displayed within Textbox of Select */
@@ -70,6 +75,12 @@ OptionRow.propTypes = {
70
75
  * @private
71
76
  * @ignore
72
77
  * True when option should be hidden from the view (prop added by the SelectList component) */
73
- hidden: PropTypes.bool
78
+ hidden: PropTypes.bool,
79
+
80
+ /**
81
+ * @private
82
+ * @ignore
83
+ * object containing CSS styles to be passed to the underlying DOM element */
84
+ style: PropTypes.object
74
85
  };
75
86
  export default OptionRow;
@@ -1,6 +1,10 @@
1
1
  import styled, { css } from "styled-components";
2
2
  const StyledOptionRow = styled.tr`
3
3
  cursor: pointer;
4
+ position: absolute;
5
+ top: 0;
6
+ left: 0;
7
+ width: 100%;
4
8
 
5
9
  ${({
6
10
  hidden
@@ -3,12 +3,22 @@ import { baseTheme } from "../../../style/themes";
3
3
  const StyledSelectListContainer = styled.div`
4
4
  background-color: white;
5
5
  box-shadow: var(--boxShadow100);
6
- overflow: hidden;
7
6
  animation: fadeIn 250ms ease-out;
8
7
  position: absolute;
9
8
  z-index: ${({
10
9
  theme
11
10
  }) => theme.zIndex.popover};
11
+ max-height: ${({
12
+ maxHeight
13
+ }) => `${maxHeight}px`};
14
+ overflow-y: auto;
15
+ display: flex;
16
+ flex-wrap: wrap;
17
+ align-items: flex-start;
18
+
19
+ ${({
20
+ isLoading
21
+ }) => isLoading && "min-height: 150px"};
12
22
 
13
23
  @keyframes fadeIn {
14
24
  0% {
@@ -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(
@@ -72,7 +72,8 @@ const InputIconToggle = ({
72
72
  onFocus: onFocus,
73
73
  onBlur: onBlur,
74
74
  onMouseDown: onMouseDown,
75
- tabIndex: iconTabIndex
75
+ tabIndex: iconTabIndex,
76
+ "data-element": "input-icon-toggle"
76
77
  }, /*#__PURE__*/_react.default.createElement(_icon.default, {
77
78
  type: type
78
79
  }));
@@ -26,6 +26,8 @@ export interface LinkProps extends StyledLinkProps, React.AriaAttributes {
26
26
  ariaLabel?: string;
27
27
  /** allows to set rel property in <a> tag */
28
28
  rel?: string;
29
+ /** @ignore @private internal prop to be set when no href or onClick passed */
30
+ placeholderTabIndex?: boolean;
29
31
  }
30
32
  export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLButtonElement | HTMLLinkElement>>;
31
33
  export default Link;
@@ -45,6 +45,7 @@ const Link = /*#__PURE__*/_react.default.forwardRef(({
45
45
  target,
46
46
  variant = "default",
47
47
  isDarkBackground,
48
+ placeholderTabIndex,
48
49
  ...rest
49
50
  }, ref) => {
50
51
  const l = (0, _useLocale.default)();
@@ -104,7 +105,11 @@ const Link = /*#__PURE__*/_react.default.forwardRef(({
104
105
  type = "button";
105
106
  }
106
107
 
107
- return /*#__PURE__*/_react.default.createElement(type, componentProps, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, renderLinkIcon(), /*#__PURE__*/_react.default.createElement(_link.StyledContent, null, isSkipLink ? l.link.skipLinkLabel() : children), renderLinkIcon("right")));
108
+ return /*#__PURE__*/_react.default.createElement(type, { ...componentProps,
109
+ ...(placeholderTabIndex && href === undefined && !onClick && {
110
+ tabIndex: -1
111
+ })
112
+ }, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, renderLinkIcon(), /*#__PURE__*/_react.default.createElement(_link.StyledContent, null, isSkipLink ? l.link.skipLinkLabel() : children), renderLinkIcon("right")));
108
113
  };
109
114
 
110
115
  return /*#__PURE__*/_react.default.createElement(_link.StyledLink, _extends({
@@ -182,6 +187,7 @@ Link.propTypes = {
182
187
  "onClick": _propTypes.default.func,
183
188
  "onKeyDown": _propTypes.default.func,
184
189
  "onMouseDown": _propTypes.default.func,
190
+ "placeholderTabIndex": _propTypes.default.bool,
185
191
  "rel": _propTypes.default.string,
186
192
  "target": _propTypes.default.string,
187
193
  "tooltipMessage": _propTypes.default.string,
@@ -1,2 +1,4 @@
1
- export function characterNavigation(inputString: any, focusableItems: any, currentFocusedIndex: any): any;
2
- export function menuKeyboardNavigation(event: any, focusableItems: any): number | undefined;
1
+ import React from "react";
2
+ declare function characterNavigation(inputString?: string, focusableItems?: Element[]): Element | undefined;
3
+ declare function menuKeyboardNavigation(event: React.KeyboardEvent, focusableItems: Element[]): number | undefined;
4
+ export { characterNavigation, menuKeyboardNavigation };
@@ -8,31 +8,32 @@ exports.menuKeyboardNavigation = menuKeyboardNavigation;
8
8
 
9
9
  var _events = _interopRequireDefault(require("../../../../__internal__/utils/helpers/events"));
10
10
 
11
- var _menuItem = _interopRequireDefault(require("../../menu-item"));
11
+ var _locators = require("../locators");
12
12
 
13
13
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
14
 
15
- function characterNavigation(inputString, focusableItems, currentFocusedIndex) {
16
- if (!inputString) return currentFocusedIndex;
15
+ function characterNavigation(inputString, focusableItems) {
16
+ if (!inputString || !focusableItems) return undefined;
17
17
 
18
- const getNodeText = node => {
19
- if (node instanceof Array) return node.map(getNodeText).join("");
20
- if (typeof node === "object" && node) return getNodeText(node.props.children);
21
- return node;
18
+ const getInnerText = element => {
19
+ var _element$textContent, _element$textContent$;
20
+
21
+ return element === null || element === void 0 ? void 0 : (_element$textContent = element.textContent) === null || _element$textContent === void 0 ? void 0 : (_element$textContent$ = _element$textContent.split("\n")) === null || _element$textContent$ === void 0 ? void 0 : _element$textContent$.map(text => text.trim()).join(" ");
22
22
  };
23
23
 
24
24
  const getMenuText = element => {
25
- if (element.submenu) {
26
- return element.submenu;
27
- }
25
+ var _getInnerText;
28
26
 
29
- return getNodeText(element.children);
27
+ return (_getInnerText = getInnerText(element)) === null || _getInnerText === void 0 ? void 0 : _getInnerText.toLowerCase();
30
28
  };
31
29
 
32
- const itemsList = focusableItems.map(item => item && item.type === _menuItem.default && getMenuText(item.props) && getMenuText(item.props).toLowerCase());
33
- const matchingItem = itemsList.find(item => item && item.startsWith(inputString));
34
- const matchingIndex = itemsList.indexOf(matchingItem);
35
- return matchingIndex === -1 ? currentFocusedIndex : matchingIndex;
30
+ const matchingItem = focusableItems.find(item => {
31
+ var _getMenuText;
32
+
33
+ if (!(item !== null && item !== void 0 && item.getAttribute("data-component"))) return false;
34
+ return [_locators.MENU_ITEM, _locators.SCROLLABLE_BLOCK_PARENT].includes(item.getAttribute("data-component")) && ((_getMenuText = getMenuText(item)) === null || _getMenuText === void 0 ? void 0 : _getMenuText.startsWith(inputString.toLowerCase()));
35
+ });
36
+ return matchingItem;
36
37
  }
37
38
 
38
39
  function menuKeyboardNavigation(event, focusableItems) {