@itwin/itwinui-react 1.38.0 → 1.40.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 (106) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/cjs/core/Carousel/Carousel.js +21 -12
  3. package/cjs/core/Carousel/CarouselContext.d.ts +4 -2
  4. package/cjs/core/Carousel/CarouselDotsList.js +1 -0
  5. package/cjs/core/Carousel/CarouselNavigation.js +8 -10
  6. package/cjs/core/Carousel/CarouselSlide.js +3 -7
  7. package/cjs/core/Carousel/CarouselSlider.js +23 -28
  8. package/cjs/core/ColorPicker/ColorPickerContext.d.ts +2 -2
  9. package/cjs/core/ComboBox/ComboBox.d.ts +17 -1
  10. package/cjs/core/ComboBox/ComboBox.js +50 -24
  11. package/cjs/core/ComboBox/ComboBoxDropdown.d.ts +5 -6
  12. package/cjs/core/ComboBox/ComboBoxInput.js +28 -9
  13. package/cjs/core/ComboBox/ComboBoxMenu.js +46 -2
  14. package/cjs/core/ComboBox/ComboBoxMenuItem.js +2 -3
  15. package/cjs/core/ComboBox/helpers.d.ts +8 -3
  16. package/cjs/core/ComboBox/helpers.js +1 -1
  17. package/cjs/core/DatePicker/DatePicker.d.ts +1 -1
  18. package/cjs/core/Menu/MenuItemSkeleton.d.ts +32 -0
  19. package/cjs/core/Menu/MenuItemSkeleton.js +53 -0
  20. package/cjs/core/Menu/index.d.ts +2 -0
  21. package/cjs/core/Menu/index.js +3 -1
  22. package/cjs/core/Select/Select.js +1 -1
  23. package/cjs/core/Table/Table.js +68 -25
  24. package/cjs/core/Table/TableCell.js +10 -3
  25. package/cjs/core/Table/TablePaginator.js +1 -1
  26. package/cjs/core/Table/TableRowMemoized.js +5 -1
  27. package/cjs/core/Table/actionHandlers/resizeHandler.d.ts +8 -0
  28. package/cjs/core/Table/actionHandlers/selectHandler.d.ts +4 -0
  29. package/cjs/core/Table/columns/selectionColumn.js +4 -2
  30. package/cjs/core/Table/filters/tableFilters.d.ts +3 -3
  31. package/cjs/core/Table/hooks/index.d.ts +1 -0
  32. package/cjs/core/Table/hooks/index.js +3 -1
  33. package/cjs/core/Table/hooks/useExpanderCell.js +11 -1
  34. package/cjs/core/Table/hooks/useStickyColumns.d.ts +2 -0
  35. package/cjs/core/Table/hooks/useStickyColumns.js +84 -0
  36. package/cjs/core/Table/utils.d.ts +1 -0
  37. package/cjs/core/Table/utils.js +36 -1
  38. package/cjs/core/Toast/Toaster.d.ts +1 -1
  39. package/cjs/core/Toast/Toaster.js +72 -29
  40. package/cjs/core/index.d.ts +2 -2
  41. package/cjs/core/index.js +4 -3
  42. package/cjs/core/utils/components/Popover.d.ts +1 -18
  43. package/cjs/core/utils/components/VirtualScroll.d.ts +35 -1
  44. package/cjs/core/utils/components/VirtualScroll.js +159 -26
  45. package/cjs/core/utils/components/VisuallyHidden.d.ts +9 -0
  46. package/cjs/core/utils/components/VisuallyHidden.js +44 -0
  47. package/cjs/core/utils/components/WithCSSTransition.d.ts +1 -2
  48. package/cjs/core/utils/components/icons.d.ts +4 -4
  49. package/cjs/core/utils/components/index.d.ts +1 -0
  50. package/cjs/core/utils/components/index.js +1 -0
  51. package/cjs/core/utils/hooks/useOverflow.js +4 -2
  52. package/cjs/core/utils/hooks/useTheme.d.ts +1 -1
  53. package/cjs/types/react-table-config.d.ts +9 -0
  54. package/esm/core/Carousel/Carousel.js +21 -12
  55. package/esm/core/Carousel/CarouselContext.d.ts +4 -2
  56. package/esm/core/Carousel/CarouselDotsList.js +1 -0
  57. package/esm/core/Carousel/CarouselNavigation.js +8 -10
  58. package/esm/core/Carousel/CarouselSlide.js +3 -7
  59. package/esm/core/Carousel/CarouselSlider.js +24 -29
  60. package/esm/core/ColorPicker/ColorPickerContext.d.ts +2 -2
  61. package/esm/core/ComboBox/ComboBox.d.ts +17 -1
  62. package/esm/core/ComboBox/ComboBox.js +50 -24
  63. package/esm/core/ComboBox/ComboBoxDropdown.d.ts +5 -6
  64. package/esm/core/ComboBox/ComboBoxInput.js +28 -9
  65. package/esm/core/ComboBox/ComboBoxMenu.js +47 -3
  66. package/esm/core/ComboBox/ComboBoxMenuItem.js +2 -3
  67. package/esm/core/ComboBox/helpers.d.ts +8 -3
  68. package/esm/core/ComboBox/helpers.js +1 -1
  69. package/esm/core/DatePicker/DatePicker.d.ts +1 -1
  70. package/esm/core/Menu/MenuItemSkeleton.d.ts +32 -0
  71. package/esm/core/Menu/MenuItemSkeleton.js +46 -0
  72. package/esm/core/Menu/index.d.ts +2 -0
  73. package/esm/core/Menu/index.js +1 -0
  74. package/esm/core/Select/Select.js +1 -1
  75. package/esm/core/Table/Table.js +70 -27
  76. package/esm/core/Table/TableCell.js +11 -4
  77. package/esm/core/Table/TablePaginator.js +1 -1
  78. package/esm/core/Table/TableRowMemoized.js +5 -1
  79. package/esm/core/Table/actionHandlers/resizeHandler.d.ts +8 -0
  80. package/esm/core/Table/actionHandlers/selectHandler.d.ts +4 -0
  81. package/esm/core/Table/columns/selectionColumn.js +4 -2
  82. package/esm/core/Table/filters/tableFilters.d.ts +3 -3
  83. package/esm/core/Table/hooks/index.d.ts +1 -0
  84. package/esm/core/Table/hooks/index.js +1 -0
  85. package/esm/core/Table/hooks/useExpanderCell.js +8 -1
  86. package/esm/core/Table/hooks/useStickyColumns.d.ts +2 -0
  87. package/esm/core/Table/hooks/useStickyColumns.js +80 -0
  88. package/esm/core/Table/utils.d.ts +1 -0
  89. package/esm/core/Table/utils.js +34 -0
  90. package/esm/core/Toast/Toaster.d.ts +1 -1
  91. package/esm/core/Toast/Toaster.js +50 -30
  92. package/esm/core/index.d.ts +2 -2
  93. package/esm/core/index.js +1 -1
  94. package/esm/core/utils/components/Popover.d.ts +1 -18
  95. package/esm/core/utils/components/VirtualScroll.d.ts +35 -1
  96. package/esm/core/utils/components/VirtualScroll.js +157 -25
  97. package/esm/core/utils/components/VisuallyHidden.d.ts +9 -0
  98. package/esm/core/utils/components/VisuallyHidden.js +38 -0
  99. package/esm/core/utils/components/WithCSSTransition.d.ts +1 -2
  100. package/esm/core/utils/components/icons.d.ts +4 -4
  101. package/esm/core/utils/components/index.d.ts +1 -0
  102. package/esm/core/utils/components/index.js +1 -0
  103. package/esm/core/utils/hooks/useOverflow.js +4 -2
  104. package/esm/core/utils/hooks/useTheme.d.ts +1 -1
  105. package/esm/types/react-table-config.d.ts +9 -0
  106. package/package.json +24 -39
@@ -5,7 +5,7 @@ export declare const ColorPickerContext: React.Context<{
5
5
  setActiveColor: (color: ColorValue | ((prevColor: ColorValue) => ColorValue)) => void;
6
6
  hsvColor: HsvColor;
7
7
  onChangeComplete?: ((color: ColorValue) => void) | undefined;
8
- applyHsvColorChange: (newColor: HsvColor, selectionChanged: boolean, newColorValue?: ColorValue | undefined) => void;
8
+ applyHsvColorChange: (newColor: HsvColor, selectionChanged: boolean, newColorValue?: ColorValue) => void;
9
9
  showAlpha: boolean;
10
10
  } | undefined>;
11
11
  export declare const useColorPickerContext: () => {
@@ -13,6 +13,6 @@ export declare const useColorPickerContext: () => {
13
13
  setActiveColor: (color: ColorValue | ((prevColor: ColorValue) => ColorValue)) => void;
14
14
  hsvColor: HsvColor;
15
15
  onChangeComplete?: ((color: ColorValue) => void) | undefined;
16
- applyHsvColorChange: (newColor: HsvColor, selectionChanged: boolean, newColorValue?: ColorValue | undefined) => void;
16
+ applyHsvColorChange: (newColor: HsvColor, selectionChanged: boolean, newColorValue?: ColorValue) => void;
17
17
  showAlpha: boolean;
18
18
  };
@@ -35,9 +35,10 @@ export declare type ComboBoxProps<T> = {
35
35
  dropdownMenuProps?: PopoverProps;
36
36
  /**
37
37
  * Message shown when no options are available.
38
+ * If `JSX.Element` is provided, it will be rendered as is and won't be wrapped with `MenuExtraContent`.
38
39
  * @default 'No options found'
39
40
  */
40
- emptyStateMessage?: string;
41
+ emptyStateMessage?: React.ReactNode;
41
42
  /**
42
43
  * A custom item renderer can be specified to control the rendering.
43
44
  *
@@ -51,6 +52,21 @@ export declare type ComboBoxProps<T> = {
51
52
  id: string;
52
53
  index: number;
53
54
  }) => JSX.Element;
55
+ /**
56
+ * If enabled, virtualization is used for the scrollable dropdown list.
57
+ * Use it if you expect a very long list of items.
58
+ * @default false
59
+ * @beta
60
+ */
61
+ enableVirtualization?: boolean;
62
+ /**
63
+ * Callback fired when dropdown menu is opened.
64
+ */
65
+ onShow?: () => void;
66
+ /**
67
+ * Callback fired when dropdown menu is closed.
68
+ */
69
+ onHide?: () => void;
54
70
  } & Pick<InputContainerProps, 'status'> & Omit<CommonProps, 'title'>;
55
71
  /**
56
72
  * ComboBox component that allows typing a value to filter the options in dropdown list.
@@ -56,8 +56,8 @@ var getOptionId = function (option, idPrefix) {
56
56
  * />
57
57
  */
58
58
  export var ComboBox = function (props) {
59
- var _a;
60
- var options = props.options, valueProp = props.value, onChange = props.onChange, filterFunction = props.filterFunction, inputProps = props.inputProps, dropdownMenuProps = props.dropdownMenuProps, _b = props.emptyStateMessage, emptyStateMessage = _b === void 0 ? 'No options found' : _b, itemRenderer = props.itemRenderer, rest = __rest(props, ["options", "value", "onChange", "filterFunction", "inputProps", "dropdownMenuProps", "emptyStateMessage", "itemRenderer"]);
59
+ var _a, _b;
60
+ var options = props.options, valueProp = props.value, onChange = props.onChange, filterFunction = props.filterFunction, inputProps = props.inputProps, dropdownMenuProps = props.dropdownMenuProps, _c = props.emptyStateMessage, emptyStateMessage = _c === void 0 ? 'No options found' : _c, itemRenderer = props.itemRenderer, _d = props.enableVirtualization, enableVirtualization = _d === void 0 ? false : _d, onShow = props.onShow, onHide = props.onHide, rest = __rest(props, ["options", "value", "onChange", "filterFunction", "inputProps", "dropdownMenuProps", "emptyStateMessage", "itemRenderer", "enableVirtualization", "onShow", "onHide"]);
61
61
  // Generate a stateful random id if not specified
62
62
  var id = React.useState(function () {
63
63
  var _a, _b;
@@ -89,17 +89,18 @@ export var ComboBox = function (props) {
89
89
  });
90
90
  }
91
91
  // Reducer where all the component-wide state is stored
92
- var _c = React.useReducer(comboBoxReducer, {
92
+ var _e = React.useReducer(comboBoxReducer, {
93
93
  isOpen: false,
94
94
  selectedIndex: -1,
95
95
  focusedIndex: -1,
96
- }), _d = _c[0], isOpen = _d.isOpen, selectedIndex = _d.selectedIndex, focusedIndex = _d.focusedIndex, dispatch = _c[1];
97
- React.useEffect(function () {
96
+ }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
97
+ React.useLayoutEffect(function () {
98
98
  var _a, _b;
99
99
  // When the dropdown opens
100
100
  if (isOpen) {
101
101
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
102
102
  setFilteredOptions(options); // Reset the filtered list
103
+ dispatch(['focus']);
103
104
  }
104
105
  // When the dropdown closes
105
106
  else {
@@ -112,19 +113,30 @@ export var ComboBox = function (props) {
112
113
  }
113
114
  }, [isOpen, options, selectedIndex]);
114
115
  // Set min-width of menu to be same as input
115
- var _e = React.useState(0), minWidth = _e[0], setMinWidth = _e[1];
116
+ var _g = React.useState(0), minWidth = _g[0], setMinWidth = _g[1];
116
117
  React.useEffect(function () {
117
118
  if (inputRef.current) {
118
119
  setMinWidth(inputRef.current.offsetWidth);
119
120
  }
120
121
  }, [isOpen]);
121
- // Initialize filtered options to the latest value options
122
- var _f = React.useState(options), filteredOptions = _f[0], setFilteredOptions = _f[1];
122
+ // Update filtered options to the latest value options according to input value
123
+ var _h = React.useState(options), filteredOptions = _h[0], setFilteredOptions = _h[1];
123
124
  React.useEffect(function () {
124
- setFilteredOptions(options);
125
+ var _a;
126
+ if (inputValue) {
127
+ setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(options, inputValue)) !== null && _a !== void 0 ? _a : options.filter(function (option) {
128
+ return option.label.toLowerCase().includes(inputValue.toLowerCase());
129
+ }));
130
+ }
131
+ else {
132
+ setFilteredOptions(options);
133
+ }
134
+ dispatch(['focus']);
135
+ // Only need to call on options update
136
+ // eslint-disable-next-line react-hooks/exhaustive-deps
125
137
  }, [options]);
126
138
  // Filter options based on input value
127
- var _g = React.useState((_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.value) !== null && _a !== void 0 ? _a : ''), inputValue = _g[0], setInputValue = _g[1];
139
+ var _j = React.useState((_b = (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.value) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''), inputValue = _j[0], setInputValue = _j[1];
128
140
  var handleOnInput = React.useCallback(function (event) {
129
141
  var _a, _b;
130
142
  var value = event.currentTarget.value;
@@ -133,12 +145,11 @@ export var ComboBox = function (props) {
133
145
  setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(options, value)) !== null && _a !== void 0 ? _a : options.filter(function (option) {
134
146
  return option.label.toLowerCase().includes(value.toLowerCase());
135
147
  }));
148
+ if (focusedIndex != -1) {
149
+ dispatch(['focus', -1]);
150
+ }
136
151
  (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
137
- }, [filterFunction, inputProps, options]);
138
- // Reset focused item when filteredOptions change
139
- React.useEffect(function () {
140
- dispatch(['focus']);
141
- }, [filteredOptions]);
152
+ }, [filterFunction, focusedIndex, inputProps, options]);
142
153
  // When the value prop changes, update the selectedIndex
143
154
  React.useEffect(function () {
144
155
  dispatch([
@@ -157,7 +168,7 @@ export var ComboBox = function (props) {
157
168
  (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
158
169
  }
159
170
  }, [options, selectedIndex, valueProp]);
160
- var getMenuItem = React.useCallback(function (option) {
171
+ var getMenuItem = React.useCallback(function (option, filteredIndex) {
161
172
  var optionId = getOptionId(option, id);
162
173
  var __originalIndex = optionsExtraInfoRef.current[optionId].__originalIndex;
163
174
  var customItem = itemRenderer
@@ -172,6 +183,7 @@ export var ComboBox = function (props) {
172
183
  onClick: function (e) {
173
184
  var _a, _b;
174
185
  dispatch(['select', __originalIndex]);
186
+ dispatch(['close']);
175
187
  (_b = (_a = customItem.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
176
188
  },
177
189
  // ComboBox.MenuItem handles scrollIntoView, data-iui-index and iui-focused through context
@@ -180,22 +192,36 @@ export var ComboBox = function (props) {
180
192
  'iui-focused': focusedIndex === __originalIndex,
181
193
  }),
182
194
  'data-iui-index': __originalIndex,
195
+ 'data-iui-filtered-index': filteredIndex,
183
196
  ref: mergeRefs(customItem.props.ref, function (el) {
184
- // will need to check for virtualization here too
185
- if (focusedIndex === __originalIndex) {
197
+ if (!enableVirtualization && focusedIndex === __originalIndex) {
186
198
  el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
187
199
  }
188
200
  }),
189
- })) : (React.createElement(ComboBoxMenuItem, __assign({ key: optionId, id: optionId }, option, { isSelected: selectedIndex === __originalIndex, onClick: function () { return dispatch(['select', __originalIndex]); }, index: __originalIndex }), option.label));
190
- }, [focusedIndex, id, itemRenderer, selectedIndex]);
201
+ })) : (React.createElement(ComboBoxMenuItem, __assign({ key: optionId, id: optionId }, option, { isSelected: selectedIndex === __originalIndex, onClick: function () {
202
+ dispatch(['select', __originalIndex]);
203
+ dispatch(['close']);
204
+ }, index: __originalIndex, "data-iui-filtered-index": filteredIndex }), option.label));
205
+ }, [enableVirtualization, focusedIndex, id, itemRenderer, selectedIndex]);
206
+ var emptyContent = React.useMemo(function () { return (React.createElement(React.Fragment, null, React.isValidElement(emptyStateMessage) ? (emptyStateMessage) : (React.createElement(MenuExtraContent, null,
207
+ React.createElement(Text, { isMuted: true }, emptyStateMessage))))); }, [emptyStateMessage]);
191
208
  return (React.createElement(ComboBoxRefsContext.Provider, { value: { inputRef: inputRef, menuRef: menuRef, toggleButtonRef: toggleButtonRef, optionsExtraInfoRef: optionsExtraInfoRef } },
192
209
  React.createElement(ComboBoxActionContext.Provider, { value: dispatch },
193
- React.createElement(ComboBoxStateContext.Provider, { value: { id: id, minWidth: minWidth, isOpen: isOpen, focusedIndex: focusedIndex } },
210
+ React.createElement(ComboBoxStateContext.Provider, { value: {
211
+ id: id,
212
+ minWidth: minWidth,
213
+ isOpen: isOpen,
214
+ focusedIndex: focusedIndex,
215
+ enableVirtualization: enableVirtualization,
216
+ filteredOptions: filteredOptions,
217
+ getMenuItem: getMenuItem,
218
+ } },
194
219
  React.createElement(ComboBoxInputContainer, __assign({ disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled }, rest),
195
220
  React.createElement(ComboBoxInput, __assign({ value: inputValue }, inputProps, { onChange: handleOnInput })),
196
221
  React.createElement(ComboBoxEndIcon, { disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, isOpen: isOpen })),
197
- React.createElement(ComboBoxDropdown, __assign({}, dropdownMenuProps),
198
- React.createElement(ComboBoxMenu, null, filteredOptions.length > 0 ? (filteredOptions.map(getMenuItem)) : (React.createElement(MenuExtraContent, null,
199
- React.createElement(Text, { isMuted: true }, emptyStateMessage)))))))));
222
+ React.createElement(ComboBoxDropdown, __assign({}, dropdownMenuProps, { onShow: onShow, onHide: onHide }),
223
+ React.createElement(ComboBoxMenu, null, filteredOptions.length > 0 && !enableVirtualization
224
+ ? filteredOptions.map(getMenuItem)
225
+ : emptyContent))))));
200
226
  };
201
227
  export default ComboBox;
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
- export declare const ComboBoxDropdown: React.ForwardRefExoticComponent<Pick<{
3
- visible?: boolean | undefined;
4
- trigger?: string | undefined;
5
- placement?: import("@popperjs/core").Placement | undefined;
6
- } & Omit<import("@tippyjs/react").TippyProps, "placement" | "trigger" | "visible"> & {
2
+ import { PopoverProps } from '../utils';
3
+ declare type ComboBoxDropdownProps = PopoverProps & {
7
4
  children: JSX.Element;
8
- }, "disabled" | "children" | "placement" | "trigger" | "visible" | "content" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "offset" | "plugins" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "role" | "theme" | "zIndex" | "className" | "singleton" | "reference"> & React.RefAttributes<Element>>;
5
+ };
6
+ export declare const ComboBoxDropdown: React.ForwardRefExoticComponent<Pick<ComboBoxDropdownProps, "disabled" | "children" | "placement" | "trigger" | "visible" | "content" | "render" | "animateFill" | "appendTo" | "aria" | "delay" | "duration" | "followCursor" | "getReferenceClientRect" | "hideOnClick" | "ignoreAttributes" | "inlinePositioning" | "interactive" | "interactiveBorder" | "interactiveDebounce" | "moveTransition" | "offset" | "plugins" | "popperOptions" | "showOnCreate" | "sticky" | "touch" | "triggerTarget" | "onAfterUpdate" | "onBeforeUpdate" | "onCreate" | "onDestroy" | "onHidden" | "onHide" | "onMount" | "onShow" | "onShown" | "onTrigger" | "onUntrigger" | "onClickOutside" | "allowHTML" | "animation" | "arrow" | "inertia" | "maxWidth" | "role" | "theme" | "zIndex" | "className" | "singleton" | "reference"> & React.RefAttributes<Element>>;
7
+ export {};
@@ -30,7 +30,7 @@ import { useSafeContext, useMergedRefs } from '../utils';
30
30
  import { ComboBoxStateContext, ComboBoxActionContext, ComboBoxRefsContext, } from './helpers';
31
31
  export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
32
32
  var onKeyDownProp = props.onKeyDown, onFocusProp = props.onFocus, rest = __rest(props, ["onKeyDown", "onFocus"]);
33
- var _a = useSafeContext(ComboBoxStateContext), isOpen = _a.isOpen, id = _a.id, focusedIndex = _a.focusedIndex;
33
+ var _a = useSafeContext(ComboBoxStateContext), isOpen = _a.isOpen, id = _a.id, focusedIndex = _a.focusedIndex, enableVirtualization = _a.enableVirtualization;
34
34
  var dispatch = useSafeContext(ComboBoxActionContext);
35
35
  var _b = useSafeContext(ComboBoxRefsContext), inputRef = _b.inputRef, menuRef = _b.menuRef, optionsExtraInfoRef = _b.optionsExtraInfoRef;
36
36
  var refs = useMergedRefs(inputRef, forwardedRef);
@@ -43,7 +43,7 @@ export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
43
43
  return ((_c = (_b = (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("[data-iui-index=\"".concat(index, "\"]"))) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : '');
44
44
  };
45
45
  var handleKeyDown = React.useCallback(function (event) {
46
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
46
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
47
47
  onKeyDownProp === null || onKeyDownProp === void 0 ? void 0 : onKeyDownProp(event);
48
48
  var length = (_a = Object.keys(optionsExtraInfoRef.current).length) !== null && _a !== void 0 ? _a : 0;
49
49
  switch (event.key) {
@@ -56,15 +56,21 @@ export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
56
56
  return;
57
57
  }
58
58
  if (focusedIndexRef.current === -1) {
59
+ var currentElement = (_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.querySelector('[data-iui-index]');
59
60
  return dispatch([
60
61
  'focus',
61
- (_c = (_b = Object.values(optionsExtraInfoRef.current)) === null || _b === void 0 ? void 0 : _b[0].__originalIndex) !== null && _c !== void 0 ? _c : -1,
62
+ Number((_c = currentElement === null || currentElement === void 0 ? void 0 : currentElement.getAttribute('data-iui-index')) !== null && _c !== void 0 ? _c : 0),
62
63
  ]);
63
64
  }
65
+ // If virtualization is enabled, dont let round scrolling
66
+ if (enableVirtualization &&
67
+ !((_e = (_d = menuRef.current) === null || _d === void 0 ? void 0 : _d.querySelector("[data-iui-index=\"".concat(focusedIndexRef.current, "\"]"))) === null || _e === void 0 ? void 0 : _e.nextElementSibling)) {
68
+ return;
69
+ }
64
70
  var nextIndex = focusedIndexRef.current;
65
71
  do {
66
- var currentElement = (_d = menuRef.current) === null || _d === void 0 ? void 0 : _d.querySelector("[data-iui-index=\"".concat(nextIndex, "\"]"));
67
- var nextElement = (_e = currentElement === null || currentElement === void 0 ? void 0 : currentElement.nextElementSibling) !== null && _e !== void 0 ? _e : (_f = menuRef.current) === null || _f === void 0 ? void 0 : _f.querySelector('[data-iui-index]');
72
+ var currentElement = (_f = menuRef.current) === null || _f === void 0 ? void 0 : _f.querySelector("[data-iui-index=\"".concat(nextIndex, "\"]"));
73
+ var nextElement = (_g = currentElement === null || currentElement === void 0 ? void 0 : currentElement.nextElementSibling) !== null && _g !== void 0 ? _g : (_h = menuRef.current) === null || _h === void 0 ? void 0 : _h.querySelector('[data-iui-index]');
68
74
  nextIndex = Number(nextElement === null || nextElement === void 0 ? void 0 : nextElement.getAttribute('data-iui-index'));
69
75
  if ((nextElement === null || nextElement === void 0 ? void 0 : nextElement.ariaDisabled) !== 'true') {
70
76
  return dispatch(['focus', nextIndex]);
@@ -80,16 +86,21 @@ export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
80
86
  if (length === 0) {
81
87
  return;
82
88
  }
89
+ // If virtualization is enabled, dont let round scrolling
90
+ if (enableVirtualization &&
91
+ !((_k = (_j = menuRef.current) === null || _j === void 0 ? void 0 : _j.querySelector("[data-iui-index=\"".concat(focusedIndexRef.current, "\"]"))) === null || _k === void 0 ? void 0 : _k.previousElementSibling)) {
92
+ return;
93
+ }
83
94
  if (focusedIndexRef.current === -1) {
84
95
  return dispatch([
85
96
  'focus',
86
- (_h = (_g = Object.values(optionsExtraInfoRef.current)) === null || _g === void 0 ? void 0 : _g[length - 1].__originalIndex) !== null && _h !== void 0 ? _h : -1,
97
+ (_m = (_l = Object.values(optionsExtraInfoRef.current)) === null || _l === void 0 ? void 0 : _l[length - 1].__originalIndex) !== null && _m !== void 0 ? _m : -1,
87
98
  ]);
88
99
  }
89
100
  var prevIndex = focusedIndexRef.current;
90
101
  do {
91
- var currentElement = (_j = menuRef.current) === null || _j === void 0 ? void 0 : _j.querySelector("[data-iui-index=\"".concat(prevIndex, "\"]"));
92
- var prevElement = (_k = currentElement === null || currentElement === void 0 ? void 0 : currentElement.previousElementSibling) !== null && _k !== void 0 ? _k : (_l = menuRef.current) === null || _l === void 0 ? void 0 : _l.querySelector('[data-iui-index]:last-of-type');
102
+ var currentElement = (_o = menuRef.current) === null || _o === void 0 ? void 0 : _o.querySelector("[data-iui-index=\"".concat(prevIndex, "\"]"));
103
+ var prevElement = (_p = currentElement === null || currentElement === void 0 ? void 0 : currentElement.previousElementSibling) !== null && _p !== void 0 ? _p : (_q = menuRef.current) === null || _q === void 0 ? void 0 : _q.querySelector('[data-iui-index]:last-of-type');
93
104
  prevIndex = Number(prevElement === null || prevElement === void 0 ? void 0 : prevElement.getAttribute('data-iui-index'));
94
105
  if ((prevElement === null || prevElement === void 0 ? void 0 : prevElement.ariaDisabled) !== 'true') {
95
106
  return dispatch(['focus', prevIndex]);
@@ -101,6 +112,7 @@ export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
101
112
  event.preventDefault();
102
113
  if (isOpen) {
103
114
  dispatch(['select', focusedIndexRef.current]);
115
+ dispatch(['close']);
104
116
  }
105
117
  else {
106
118
  dispatch(['open']);
@@ -116,7 +128,14 @@ export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
116
128
  dispatch(['close']);
117
129
  break;
118
130
  }
119
- }, [dispatch, isOpen, menuRef, onKeyDownProp, optionsExtraInfoRef]);
131
+ }, [
132
+ dispatch,
133
+ enableVirtualization,
134
+ isOpen,
135
+ menuRef,
136
+ onKeyDownProp,
137
+ optionsExtraInfoRef,
138
+ ]);
120
139
  var handleFocus = React.useCallback(function (event) {
121
140
  dispatch(['open']);
122
141
  onFocusProp === null || onFocusProp === void 0 ? void 0 : onFocusProp(event);
@@ -27,13 +27,57 @@ var __rest = (this && this.__rest) || function (s, e) {
27
27
  import cx from 'classnames';
28
28
  import React from 'react';
29
29
  import { Menu } from '../Menu';
30
- import { useSafeContext, useMergedRefs } from '../utils';
30
+ import { Surface } from '../Surface';
31
+ import { useSafeContext, useMergedRefs, useVirtualization, mergeRefs, getWindow, } from '../utils';
31
32
  import { ComboBoxStateContext, ComboBoxRefsContext } from './helpers';
33
+ var VirtualizedComboBoxMenu = React.forwardRef(function (_a, forwardedRef) {
34
+ var _b, _c, _d;
35
+ var children = _a.children, style = _a.style, rest = __rest(_a, ["children", "style"]);
36
+ var _e = useSafeContext(ComboBoxStateContext), minWidth = _e.minWidth, id = _e.id, filteredOptions = _e.filteredOptions, getMenuItem = _e.getMenuItem, focusedIndex = _e.focusedIndex;
37
+ var menuRef = useSafeContext(ComboBoxRefsContext).menuRef;
38
+ var virtualItemRenderer = React.useCallback(function (index) {
39
+ return filteredOptions.length > 0
40
+ ? getMenuItem(filteredOptions[index], index)
41
+ : children;
42
+ }, // Here is expected empty state content
43
+ [filteredOptions, getMenuItem, children]);
44
+ var focusedVisibleIndex = React.useMemo(function () {
45
+ var _a, _b;
46
+ var currentElement = (_a = menuRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("[data-iui-index=\"".concat(focusedIndex, "\"]"));
47
+ if (!currentElement) {
48
+ return focusedIndex;
49
+ }
50
+ return Number((_b = currentElement.getAttribute('data-iui-filtered-index')) !== null && _b !== void 0 ? _b : focusedIndex);
51
+ }, [focusedIndex, menuRef]);
52
+ var _f = useVirtualization({
53
+ // 'Fool' VirtualScroll by passing length 1
54
+ // whenever there is no elements, to show empty state message
55
+ itemsLength: filteredOptions.length || 1,
56
+ itemRenderer: virtualItemRenderer,
57
+ scrollToIndex: focusedVisibleIndex,
58
+ }), outerProps = _f.outerProps, innerProps = _f.innerProps, visibleChildren = _f.visibleChildren;
59
+ var overflowY = ((_d = (_c = (_b = getWindow()) === null || _b === void 0 ? void 0 : _b.CSS) === null || _c === void 0 ? void 0 : _c.supports) === null || _d === void 0 ? void 0 : _d.call(_c, 'overflow-x: overlay'))
60
+ ? { overflowY: 'overlay' }
61
+ : { overflowY: 'auto' };
62
+ var styles = React.useMemo(function () { return ({
63
+ minWidth: minWidth,
64
+ maxWidth: "min(".concat(minWidth * 2, "px, 90vw)"),
65
+ maxHeight: 315,
66
+ }); }, [minWidth]);
67
+ return (React.createElement(Surface, __assign({ elevation: 1, style: __assign(__assign(__assign({}, styles), overflowY), style) }, rest),
68
+ React.createElement("div", __assign({}, outerProps),
69
+ React.createElement(Menu, { id: "".concat(id, "-list"), setFocus: false, role: 'listbox', ref: mergeRefs(menuRef, innerProps.ref, forwardedRef), style: innerProps.style }, visibleChildren))));
70
+ });
32
71
  export var ComboBoxMenu = React.forwardRef(function (props, forwardedRef) {
33
72
  var className = props.className, style = props.style, rest = __rest(props, ["className", "style"]);
34
- var _a = useSafeContext(ComboBoxStateContext), minWidth = _a.minWidth, id = _a.id;
73
+ var _a = useSafeContext(ComboBoxStateContext), minWidth = _a.minWidth, id = _a.id, enableVirtualization = _a.enableVirtualization;
35
74
  var menuRef = useSafeContext(ComboBoxRefsContext).menuRef;
36
75
  var refs = useMergedRefs(menuRef, forwardedRef);
37
- return (React.createElement(Menu, __assign({ id: "".concat(id, "-list"), style: React.useMemo(function () { return (__assign({ minWidth: minWidth, maxWidth: "min(".concat(minWidth * 2, "px, 90vw)"), maxHeight: 300 }, style)); }, [minWidth, style]), setFocus: false, role: 'listbox', ref: refs, className: cx('iui-scroll', className) }, rest)));
76
+ var styles = React.useMemo(function () { return ({
77
+ minWidth: minWidth,
78
+ maxWidth: "min(".concat(minWidth * 2, "px, 90vw)"),
79
+ maxHeight: 315,
80
+ }); }, [minWidth]);
81
+ return (React.createElement(React.Fragment, null, !enableVirtualization ? (React.createElement(Menu, __assign({ id: "".concat(id, "-list"), style: __assign(__assign({}, styles), style), setFocus: false, role: 'listbox', ref: refs, className: cx('iui-scroll', className) }, rest))) : (React.createElement(VirtualizedComboBoxMenu, __assign({ ref: forwardedRef }, props)))));
38
82
  });
39
83
  ComboBoxMenu.displayName = 'ComboBoxMenu';
@@ -30,10 +30,9 @@ import { useSafeContext, useMergedRefs } from '../utils';
30
30
  import { ComboBoxStateContext } from './helpers';
31
31
  export var ComboBoxMenuItem = React.memo(React.forwardRef(function (props, forwardedRef) {
32
32
  var children = props.children, isSelected = props.isSelected, disabled = props.disabled, value = props.value, onClick = props.onClick, sublabel = props.sublabel, _a = props.size, size = _a === void 0 ? !!sublabel ? 'large' : 'default' : _a, icon = props.icon, badge = props.badge, className = props.className, _b = props.role, role = _b === void 0 ? 'menuitem' : _b, index = props.index, rest = __rest(props, ["children", "isSelected", "disabled", "value", "onClick", "sublabel", "size", "icon", "badge", "className", "role", "index"]);
33
- var focusedIndex = useSafeContext(ComboBoxStateContext).focusedIndex;
33
+ var _c = useSafeContext(ComboBoxStateContext), focusedIndex = _c.focusedIndex, enableVirtualization = _c.enableVirtualization;
34
34
  var focusRef = function (el) {
35
- // will need to check for virtualization here too
36
- if (focusedIndex === index) {
35
+ if (!enableVirtualization && focusedIndex === index) {
37
36
  el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
38
37
  }
39
38
  };
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { SelectOption } from '../Select/Select';
2
3
  declare type ComboBoxAction = 'open' | 'close' | 'select' | 'focus';
3
4
  export declare const comboBoxReducer: (state: {
4
5
  isOpen: boolean;
@@ -17,11 +18,15 @@ export declare const ComboBoxRefsContext: React.Context<{
17
18
  __originalIndex: number;
18
19
  }>>;
19
20
  } | undefined>;
20
- export declare const ComboBoxStateContext: React.Context<{
21
+ declare type ComboBoxStateContextProps<T = unknown> = {
21
22
  isOpen: boolean;
22
23
  id: string;
23
24
  minWidth: number;
24
- focusedIndex?: number | undefined;
25
- } | undefined>;
25
+ enableVirtualization: boolean;
26
+ filteredOptions: SelectOption<T>[];
27
+ getMenuItem: (option: SelectOption<T>, filteredIndex?: number) => JSX.Element;
28
+ focusedIndex?: number;
29
+ };
30
+ export declare const ComboBoxStateContext: React.Context<ComboBoxStateContextProps<unknown> | undefined>;
26
31
  export declare const ComboBoxActionContext: React.Context<((x: [ComboBoxAction] | [ComboBoxAction, number]) => void) | undefined>;
27
32
  export {};
@@ -25,7 +25,7 @@ export var comboBoxReducer = function (state, _a) {
25
25
  return __assign(__assign({}, state), { isOpen: false });
26
26
  }
27
27
  case 'select': {
28
- return __assign(__assign({}, state), { isOpen: false, selectedIndex: value !== null && value !== void 0 ? value : state.selectedIndex, focusedIndex: value !== null && value !== void 0 ? value : state.focusedIndex });
28
+ return __assign(__assign({}, state), { selectedIndex: value !== null && value !== void 0 ? value : state.selectedIndex, focusedIndex: value !== null && value !== void 0 ? value : state.focusedIndex });
29
29
  }
30
30
  case 'focus': {
31
31
  return __assign(__assign({}, state), { focusedIndex: (_b = value !== null && value !== void 0 ? value : state.selectedIndex) !== null && _b !== void 0 ? _b : -1 });
@@ -5,7 +5,7 @@ import { TimePickerProps } from '../TimePicker';
5
5
  * Generate localized months and days strings using `Intl.DateTimeFormat` for passed locale to use in DatePicker component.
6
6
  * If locale is not passed, browser locale will be used.
7
7
  */
8
- export declare const generateLocalizedStrings: (locale?: string | undefined) => {
8
+ export declare const generateLocalizedStrings: (locale?: string) => {
9
9
  months: string[];
10
10
  shortDays: string[];
11
11
  days: string[];
@@ -0,0 +1,32 @@
1
+ /// <reference types="react" />
2
+ import { CommonProps } from '../utils';
3
+ import '@itwin/itwinui-css/css/menu.css';
4
+ export declare type MenuItemSkeletonProps = {
5
+ /**
6
+ * Flag whether to show skeleton for sub-label.
7
+ */
8
+ hasSublabel?: boolean;
9
+ /**
10
+ * Flag whether to show skeleton for icon.
11
+ */
12
+ hasIcon?: boolean;
13
+ /**
14
+ * Skeleton content width.
15
+ */
16
+ contentWidth?: string;
17
+ /**
18
+ * Translated strings used for accessibility.
19
+ */
20
+ translatedStrings?: {
21
+ /**
22
+ * Label for loading state. Defaults to "Loading…".
23
+ * It is only visible for the screen readers.
24
+ */
25
+ loading: string;
26
+ };
27
+ } & CommonProps;
28
+ /**
29
+ * Menu item that uses skeletons to indicate loading state.
30
+ */
31
+ export declare const MenuItemSkeleton: (props: MenuItemSkeletonProps) => JSX.Element;
32
+ export default MenuItemSkeleton;
@@ -0,0 +1,46 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __rest = (this && this.__rest) || function (s, e) {
13
+ var t = {};
14
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
+ t[p] = s[p];
16
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
+ t[p[i]] = s[p[i]];
20
+ }
21
+ return t;
22
+ };
23
+ /*---------------------------------------------------------------------------------------------
24
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
25
+ * See LICENSE.md in the project root for license terms and full copyright notice.
26
+ *--------------------------------------------------------------------------------------------*/
27
+ import React from 'react';
28
+ import cx from 'classnames';
29
+ import { useTheme, VisuallyHidden } from '../utils';
30
+ import '@itwin/itwinui-css/css/menu.css';
31
+ /**
32
+ * Menu item that uses skeletons to indicate loading state.
33
+ */
34
+ export var MenuItemSkeleton = function (props) {
35
+ var hasSublabel = props.hasSublabel, hasIcon = props.hasIcon, contentWidth = props.contentWidth, _a = props.translatedStrings, translatedStrings = _a === void 0 ? { loading: 'Loading…' } : _a, className = props.className, style = props.style, rest = __rest(props, ["hasSublabel", "hasIcon", "contentWidth", "translatedStrings", "className", "style"]);
36
+ useTheme();
37
+ return (React.createElement("li", __assign({ className: cx('iui-menu-item', 'iui-menu-item-skeleton', { 'iui-large': hasSublabel }, className), style: __assign({
38
+ '--iui-menu-item-content-skeleton-max-width': contentWidth,
39
+ }, style) }, rest),
40
+ hasIcon && React.createElement("div", { className: 'iui-icon iui-skeleton', "aria-hidden": true }),
41
+ React.createElement("span", { className: 'iui-content' },
42
+ React.createElement("div", { className: 'iui-menu-label iui-skeleton', "aria-hidden": true }),
43
+ hasSublabel && (React.createElement("div", { className: 'iui-menu-description iui-skeleton', "aria-hidden": true })),
44
+ React.createElement(VisuallyHidden, null, translatedStrings.loading))));
45
+ };
46
+ export default MenuItemSkeleton;
@@ -6,3 +6,5 @@ export { MenuDivider } from './MenuDivider';
6
6
  export type { MenuDividerProps } from './MenuDivider';
7
7
  export { MenuExtraContent } from './MenuExtraContent';
8
8
  export type { MenuExtraContentProps } from './MenuExtraContent';
9
+ export { MenuItemSkeleton } from './MenuItemSkeleton';
10
+ export type { MenuItemSkeletonProps } from './MenuItemSkeleton';
@@ -6,3 +6,4 @@ export { Menu } from './Menu';
6
6
  export { MenuItem } from './MenuItem';
7
7
  export { MenuDivider } from './MenuDivider';
8
8
  export { MenuExtraContent } from './MenuExtraContent';
9
+ export { MenuItemSkeleton } from './MenuItemSkeleton';
@@ -140,7 +140,7 @@ export var Select = function (props) {
140
140
  return options.find(function (option) { return option.value === value; });
141
141
  }, [options, value]);
142
142
  return (React.createElement("div", __assign({ className: cx('iui-input-with-icon', className), "aria-expanded": isOpen, "aria-haspopup": 'listbox', style: style }, rest),
143
- React.createElement(DropdownMenu, __assign({ menuItems: menuItems, placement: 'bottom-start', className: cx('iui-scroll', menuClassName), style: __assign({ minWidth: minWidth, maxWidth: "min(".concat(minWidth * 2, "px, 90vw)"), maxHeight: "300px" }, menuStyle), role: 'listbox', onShow: onShowHandler, onHide: onHideHandler, disabled: disabled }, popoverProps, { visible: isOpen, onClickOutside: function (_, _a) {
143
+ React.createElement(DropdownMenu, __assign({ menuItems: menuItems, placement: 'bottom-start', className: cx('iui-scroll', menuClassName), style: __assign({ minWidth: minWidth, maxWidth: "min(".concat(minWidth * 2, "px, 90vw)"), maxHeight: 315 }, menuStyle), role: 'listbox', onShow: onShowHandler, onHide: onHideHandler, disabled: disabled }, popoverProps, { visible: isOpen, onClickOutside: function (_, _a) {
144
144
  var _b;
145
145
  var target = _a.target;
146
146
  if (!((_b = toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {