@itwin/itwinui-react 1.40.0 → 1.42.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 (50) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/cjs/core/ComboBox/ComboBox.js +22 -18
  3. package/cjs/core/ErrorPage/ErrorPage.d.ts +3 -1
  4. package/cjs/core/ErrorPage/ErrorPage.js +31 -1
  5. package/cjs/core/Modal/Modal.d.ts +1 -0
  6. package/cjs/core/Modal/Modal.js +11 -8
  7. package/cjs/core/Table/Table.d.ts +23 -0
  8. package/cjs/core/Table/Table.js +15 -9
  9. package/cjs/core/Table/TableRowMemoized.d.ts +4 -0
  10. package/cjs/core/Table/TableRowMemoized.js +15 -3
  11. package/cjs/core/Table/cells/EditableCell.js +7 -2
  12. package/cjs/core/Table/hooks/index.d.ts +1 -0
  13. package/cjs/core/Table/hooks/index.js +3 -1
  14. package/cjs/core/Table/hooks/useScrollToRow.d.ts +11 -0
  15. package/cjs/core/Table/hooks/useScrollToRow.js +49 -0
  16. package/cjs/core/Tree/Tree.d.ts +9 -0
  17. package/cjs/core/Tree/Tree.js +67 -19
  18. package/cjs/core/Tree/TreeContext.d.ts +4 -0
  19. package/cjs/core/Tree/TreeNode.js +8 -9
  20. package/cjs/core/Typography/Small/Small.js +1 -1
  21. package/cjs/core/utils/components/VirtualScroll.js +2 -2
  22. package/cjs/core/utils/hooks/index.d.ts +1 -0
  23. package/cjs/core/utils/hooks/index.js +1 -0
  24. package/cjs/core/utils/hooks/useLatestRef.d.ts +9 -0
  25. package/cjs/core/utils/hooks/useLatestRef.js +26 -0
  26. package/esm/core/ComboBox/ComboBox.js +23 -19
  27. package/esm/core/ErrorPage/ErrorPage.d.ts +3 -1
  28. package/esm/core/ErrorPage/ErrorPage.js +31 -1
  29. package/esm/core/Modal/Modal.d.ts +1 -0
  30. package/esm/core/Modal/Modal.js +11 -8
  31. package/esm/core/Table/Table.d.ts +23 -0
  32. package/esm/core/Table/Table.js +17 -11
  33. package/esm/core/Table/TableRowMemoized.d.ts +4 -0
  34. package/esm/core/Table/TableRowMemoized.js +15 -3
  35. package/esm/core/Table/cells/EditableCell.js +7 -2
  36. package/esm/core/Table/hooks/index.d.ts +1 -0
  37. package/esm/core/Table/hooks/index.js +1 -0
  38. package/esm/core/Table/hooks/useScrollToRow.d.ts +11 -0
  39. package/esm/core/Table/hooks/useScrollToRow.js +42 -0
  40. package/esm/core/Tree/Tree.d.ts +9 -0
  41. package/esm/core/Tree/Tree.js +68 -20
  42. package/esm/core/Tree/TreeContext.d.ts +4 -0
  43. package/esm/core/Tree/TreeNode.js +8 -9
  44. package/esm/core/Typography/Small/Small.js +1 -1
  45. package/esm/core/utils/components/VirtualScroll.js +2 -2
  46. package/esm/core/utils/hooks/index.d.ts +1 -0
  47. package/esm/core/utils/hooks/index.js +1 -0
  48. package/esm/core/utils/hooks/useLatestRef.d.ts +9 -0
  49. package/esm/core/utils/hooks/useLatestRef.js +19 -0
  50. package/package.json +4 -4
@@ -86,7 +86,7 @@ var TreeContext_1 = require("./TreeContext");
86
86
  />
87
87
  */
88
88
  var Tree = function (props) {
89
- var data = props.data, className = props.className, nodeRenderer = props.nodeRenderer, getNode = props.getNode, rest = __rest(props, ["data", "className", "nodeRenderer", "getNode"]);
89
+ var data = props.data, className = props.className, nodeRenderer = props.nodeRenderer, getNode = props.getNode, _a = props.enableVirtualization, enableVirtualization = _a === void 0 ? false : _a, style = props.style, rest = __rest(props, ["data", "className", "nodeRenderer", "getNode", "enableVirtualization", "style"]);
90
90
  (0, utils_1.useTheme)();
91
91
  var treeRef = react_1.default.useRef(null);
92
92
  var focusedIndex = react_1.default.useRef(0);
@@ -124,7 +124,7 @@ var Tree = function (props) {
124
124
  break;
125
125
  }
126
126
  };
127
- var _a = react_1.default.useMemo(function () {
127
+ var _b = react_1.default.useMemo(function () {
128
128
  var flatList = [];
129
129
  var firstLevelNodes = [];
130
130
  var flattenNodes = function (nodes, depth, parentNode) {
@@ -153,25 +153,73 @@ var Tree = function (props) {
153
153
  };
154
154
  flattenNodes(data);
155
155
  return [flatList, firstLevelNodes];
156
- }, [data, getNode]), flatNodesList = _a[0], firstLevelNodesList = _a[1];
157
- return (react_1.default.createElement("ul", __assign({ className: (0, classnames_1.default)('iui-tree', className), role: 'tree', onKeyDown: handleKeyDown, ref: treeRef, tabIndex: 0, onFocus: function () {
158
- var _a;
159
- var items = getFocusableNodes();
160
- if (items.length > 0) {
161
- (_a = items[focusedIndex.current]) === null || _a === void 0 ? void 0 : _a.focus();
162
- }
163
- } }, rest), flatNodesList.map(function (flatNode) {
156
+ }, [data, getNode]), flatNodesList = _b[0], firstLevelNodesList = _b[1];
157
+ var itemRenderer = react_1.default.useCallback(function (index) {
164
158
  var _a, _b, _c, _d;
165
- return (react_1.default.createElement(TreeContext_1.TreeContext.Provider, { key: flatNode.nodeProps.nodeId, value: {
166
- nodeDepth: flatNode.depth,
167
- subNodeIds: flatNode.subNodeIds,
168
- groupSize: flatNode.depth === 0
159
+ var node = flatNodesList[index];
160
+ return (react_1.default.createElement(TreeContext_1.TreeContext.Provider, { key: node.nodeProps.nodeId, value: {
161
+ nodeDepth: node.depth,
162
+ subNodeIds: node.subNodeIds,
163
+ groupSize: node.depth === 0
169
164
  ? firstLevelNodesList.length
170
- : (_c = (_b = (_a = flatNode.parentNode) === null || _a === void 0 ? void 0 : _a.subNodeIds) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0,
171
- indexInGroup: flatNode.indexInGroup,
172
- parentNodeId: (_d = flatNode.parentNode) === null || _d === void 0 ? void 0 : _d.nodeProps.nodeId,
173
- } }, nodeRenderer(flatNode.nodeProps)));
174
- })));
165
+ : (_c = (_b = (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.subNodeIds) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0,
166
+ indexInGroup: node.indexInGroup,
167
+ parentNodeId: (_d = node.parentNode) === null || _d === void 0 ? void 0 : _d.nodeProps.nodeId,
168
+ scrollToParent: node.parentNode
169
+ ? function () {
170
+ var _a;
171
+ var parentNodeId = (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeProps.nodeId;
172
+ var parentNodeIndex = flatNodesList.findIndex(function (n) { return n.nodeProps.nodeId === parentNodeId; });
173
+ setScrollToIndex(parentNodeIndex);
174
+ }
175
+ : undefined,
176
+ } }, nodeRenderer(node.nodeProps)));
177
+ }, [firstLevelNodesList.length, flatNodesList, nodeRenderer]);
178
+ var _c = react_1.default.useState(), scrollToIndex = _c[0], setScrollToIndex = _c[1];
179
+ var flatNodesListRef = react_1.default.useRef(flatNodesList);
180
+ react_1.default.useEffect(function () {
181
+ flatNodesListRef.current = flatNodesList;
182
+ }, [flatNodesList]);
183
+ react_1.default.useEffect(function () {
184
+ setTimeout(function () {
185
+ var _a;
186
+ if (scrollToIndex !== undefined) {
187
+ var nodeId = flatNodesListRef.current[scrollToIndex].nodeProps.nodeId;
188
+ var nodeElement = (_a = treeRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.querySelector("#".concat(nodeId));
189
+ nodeElement === null || nodeElement === void 0 ? void 0 : nodeElement.focus();
190
+ // Need to reset that if navigating with mouse and keyboard,
191
+ // e.g. pressing arrow left to go to parent node and then with mouse
192
+ // clicking some other child node and then pressing arrow left
193
+ setScrollToIndex(undefined);
194
+ }
195
+ });
196
+ }, [scrollToIndex]);
197
+ var handleFocus = function (event) {
198
+ var _a, _b;
199
+ if ((_a = treeRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.relatedTarget)) {
200
+ return;
201
+ }
202
+ var items = getFocusableNodes();
203
+ if (items.length > 0) {
204
+ (_b = items[focusedIndex.current]) === null || _b === void 0 ? void 0 : _b.focus();
205
+ }
206
+ };
207
+ return (react_1.default.createElement(react_1.default.Fragment, null, enableVirtualization ? (react_1.default.createElement(VirtualizedTree, __assign({ flatNodesList: flatNodesList, itemRenderer: itemRenderer, scrollToIndex: scrollToIndex, onFocus: handleFocus, onKeyDown: handleKeyDown, ref: treeRef, className: className, style: style }, rest))) : (react_1.default.createElement(TreeElement, __assign({ onKeyDown: handleKeyDown, onFocus: handleFocus, className: className, style: style, ref: treeRef }, rest), flatNodesList.map(function (_, i) { return itemRenderer(i); })))));
175
208
  };
176
209
  exports.Tree = Tree;
210
+ var TreeElement = react_1.default.forwardRef(function (_a, ref) {
211
+ var children = _a.children, className = _a.className, rest = __rest(_a, ["children", "className"]);
212
+ return (react_1.default.createElement("ul", __assign({ className: (0, classnames_1.default)('iui-tree', className), role: 'tree', ref: ref, tabIndex: 0 }, rest), children));
213
+ });
214
+ // Having virtualized tree separately prevents from running all virtualization logic
215
+ var VirtualizedTree = react_1.default.forwardRef(function (_a, ref) {
216
+ var flatNodesList = _a.flatNodesList, itemRenderer = _a.itemRenderer, scrollToIndex = _a.scrollToIndex, className = _a.className, style = _a.style, rest = __rest(_a, ["flatNodesList", "itemRenderer", "scrollToIndex", "className", "style"]);
217
+ var _b = (0, utils_1.useVirtualization)({
218
+ itemsLength: flatNodesList.length,
219
+ itemRenderer: itemRenderer,
220
+ scrollToIndex: scrollToIndex,
221
+ }), outerProps = _b.outerProps, innerProps = _b.innerProps, visibleChildren = _b.visibleChildren;
222
+ return (react_1.default.createElement("div", __assign({}, __assign(__assign({}, outerProps), { className: (0, classnames_1.default)(className, outerProps.className), style: __assign(__assign({}, style), outerProps.style) })),
223
+ react_1.default.createElement(TreeElement, __assign({}, innerProps, rest, { ref: (0, utils_1.mergeRefs)(ref, innerProps.ref) }), visibleChildren)));
224
+ });
177
225
  exports.default = exports.Tree;
@@ -20,6 +20,10 @@ export declare type TreeContextProps = {
20
20
  * Node index in the list of nodes under the same parent node or in the root. Used for an accessibility attribute.
21
21
  */
22
22
  indexInGroup: number;
23
+ /**
24
+ * Function that scrolls to the node's parent node.
25
+ */
26
+ scrollToParent?: () => void;
23
27
  };
24
28
  export declare const TreeContext: React.Context<TreeContextProps | undefined>;
25
29
  export declare const useTreeContext: () => TreeContextProps;
@@ -58,7 +58,7 @@ var TreeContext_1 = require("./TreeContext");
58
58
  var TreeNode = function (props) {
59
59
  var nodeId = props.nodeId, label = props.label, sublabel = props.sublabel, children = props.children, className = props.className, icon = props.icon, _a = props.hasSubNodes, hasSubNodes = _a === void 0 ? false : _a, _b = props.isDisabled, isDisabled = _b === void 0 ? false : _b, _c = props.isExpanded, isExpanded = _c === void 0 ? false : _c, _d = props.isSelected, isSelected = _d === void 0 ? false : _d, onSelected = props.onSelected, onExpanded = props.onExpanded, checkbox = props.checkbox, expander = props.expander, rest = __rest(props, ["nodeId", "label", "sublabel", "children", "className", "icon", "hasSubNodes", "isDisabled", "isExpanded", "isSelected", "onSelected", "onExpanded", "checkbox", "expander"]);
60
60
  (0, utils_1.useTheme)();
61
- var _e = (0, TreeContext_1.useTreeContext)(), nodeDepth = _e.nodeDepth, _f = _e.subNodeIds, subNodeIds = _f === void 0 ? [] : _f, parentNodeId = _e.parentNodeId, groupSize = _e.groupSize, indexInGroup = _e.indexInGroup;
61
+ var _e = (0, TreeContext_1.useTreeContext)(), nodeDepth = _e.nodeDepth, _f = _e.subNodeIds, subNodeIds = _f === void 0 ? [] : _f, parentNodeId = _e.parentNodeId, scrollToParent = _e.scrollToParent, groupSize = _e.groupSize, indexInGroup = _e.indexInGroup;
62
62
  var _g = react_1.default.useState(false), isFocused = _g[0], setIsFocused = _g[1];
63
63
  var nodeRef = react_1.default.useRef(null);
64
64
  var styleDepth = react_1.default.useMemo(function () {
@@ -68,7 +68,7 @@ var TreeNode = function (props) {
68
68
  : { marginLeft: nodeDepth ? nodeDepth * 28 : 0 };
69
69
  }, [nodeDepth]);
70
70
  var onKeyDown = function (event) {
71
- var _a, _b, _c, _d, _e, _f, _g;
71
+ var _a, _b, _c, _d, _e, _f;
72
72
  var isNodeFocused = nodeRef.current === ((_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.activeElement);
73
73
  switch (event.key) {
74
74
  case 'ArrowLeft': {
@@ -79,20 +79,19 @@ var TreeNode = function (props) {
79
79
  break;
80
80
  }
81
81
  if (parentNodeId) {
82
- var parentNode = (_b = nodeRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.querySelector("#".concat(parentNodeId));
83
- parentNode === null || parentNode === void 0 ? void 0 : parentNode.focus();
82
+ scrollToParent === null || scrollToParent === void 0 ? void 0 : scrollToParent();
84
83
  break;
85
84
  }
86
85
  // If it is top level node (doesn't have parent node), then do nothing.
87
86
  break;
88
87
  }
89
88
  var focusableElements = (0, utils_1.getFocusableElements)(nodeRef.current);
90
- var currentIndex = focusableElements.indexOf((_c = nodeRef.current) === null || _c === void 0 ? void 0 : _c.ownerDocument.activeElement);
89
+ var currentIndex = focusableElements.indexOf((_b = nodeRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.activeElement);
91
90
  if (currentIndex === 0) {
92
- (_d = nodeRef.current) === null || _d === void 0 ? void 0 : _d.focus();
91
+ (_c = nodeRef.current) === null || _c === void 0 ? void 0 : _c.focus();
93
92
  }
94
93
  else {
95
- (_e = focusableElements[currentIndex - 1]) === null || _e === void 0 ? void 0 : _e.focus();
94
+ (_d = focusableElements[currentIndex - 1]) === null || _d === void 0 ? void 0 : _d.focus();
96
95
  }
97
96
  break;
98
97
  }
@@ -104,10 +103,10 @@ var TreeNode = function (props) {
104
103
  onExpanded(nodeId, true);
105
104
  break;
106
105
  }
107
- (_f = focusableElements[0]) === null || _f === void 0 ? void 0 : _f.focus();
106
+ (_e = focusableElements[0]) === null || _e === void 0 ? void 0 : _e.focus();
108
107
  break;
109
108
  }
110
- var currentIndex = focusableElements.indexOf((_g = nodeRef.current) === null || _g === void 0 ? void 0 : _g.ownerDocument.activeElement);
109
+ var currentIndex = focusableElements.indexOf((_f = nodeRef.current) === null || _f === void 0 ? void 0 : _f.ownerDocument.activeElement);
111
110
  if (currentIndex < focusableElements.length - 1) {
112
111
  focusableElements[currentIndex + 1].focus();
113
112
  break;
@@ -43,6 +43,6 @@ require("@itwin/itwinui-css/css/text.css");
43
43
  exports.Small = react_1.default.forwardRef(function (props, ref) {
44
44
  var className = props.className, _a = props.isMuted, isMuted = _a === void 0 ? false : _a, rest = __rest(props, ["className", "isMuted"]);
45
45
  (0, utils_1.useTheme)();
46
- return (react_1.default.createElement("p", __assign({ ref: ref, className: (0, classnames_1.default)('iui-text-small', { 'iui-text-muted': isMuted }, className) }, rest)));
46
+ return (react_1.default.createElement("small", __assign({ ref: ref, className: (0, classnames_1.default)('iui-text-small', { 'iui-text-muted': isMuted }, className) }, rest)));
47
47
  });
48
48
  exports.default = exports.Small;
@@ -291,11 +291,11 @@ var useVirtualization = function (props) {
291
291
  updateVirtualScroll();
292
292
  }, [scrollContainerHeight, updateVirtualScroll]);
293
293
  return {
294
- outerProps: __assign({ style: __assign({ overflow: 'hidden', minHeight: itemsLength > 1
294
+ outerProps: __assign({ style: __assign({ minHeight: itemsLength > 1
295
295
  ? Math.max(itemsLength - 2, 0) * childHeight.current.middle +
296
296
  childHeight.current.first +
297
297
  childHeight.current.last
298
- : childHeight.current.middle, width: '100%' }, style) }, rest),
298
+ : childHeight.current.middle, minWidth: '100%' }, style) }, rest),
299
299
  innerProps: {
300
300
  style: { willChange: 'transform' },
301
301
  ref: (0, hooks_1.mergeRefs)(parentRef), // convert object ref to callback ref for better types
@@ -7,3 +7,4 @@ export * from './useTheme';
7
7
  export * from './useIntersection';
8
8
  export * from './useMediaQuery';
9
9
  export * from './useSafeContext';
10
+ export * from './useLatestRef';
@@ -27,3 +27,4 @@ __exportStar(require("./useTheme"), exports);
27
27
  __exportStar(require("./useIntersection"), exports);
28
28
  __exportStar(require("./useMediaQuery"), exports);
29
29
  __exportStar(require("./useSafeContext"), exports);
30
+ __exportStar(require("./useLatestRef"), exports);
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ /**
3
+ * Hook that keeps track of the latest value in a ref.
4
+ * @private
5
+ * @example
6
+ * const { value } = props;
7
+ * const valueRef = useLatestRef(value);
8
+ */
9
+ export declare const useLatestRef: <T>(value: T) => React.MutableRefObject<T>;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.useLatestRef = void 0;
7
+ /*---------------------------------------------------------------------------------------------
8
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
9
+ * See LICENSE.md in the project root for license terms and full copyright notice.
10
+ *--------------------------------------------------------------------------------------------*/
11
+ var react_1 = __importDefault(require("react"));
12
+ /**
13
+ * Hook that keeps track of the latest value in a ref.
14
+ * @private
15
+ * @example
16
+ * const { value } = props;
17
+ * const valueRef = useLatestRef(value);
18
+ */
19
+ var useLatestRef = function (value) {
20
+ var valueRef = react_1.default.useRef(value);
21
+ react_1.default.useEffect(function () {
22
+ valueRef.current = value;
23
+ }, [value]);
24
+ return valueRef;
25
+ };
26
+ exports.useLatestRef = useLatestRef;
@@ -28,7 +28,7 @@ import React from 'react';
28
28
  import cx from 'classnames';
29
29
  import { MenuExtraContent } from '../Menu';
30
30
  import { Text } from '../Typography';
31
- import { useTheme, getRandomValue, mergeRefs, } from '../utils';
31
+ import { useTheme, getRandomValue, mergeRefs, useLatestRef, } from '../utils';
32
32
  import 'tippy.js/animations/shift-away.css';
33
33
  import { ComboBoxActionContext, comboBoxReducer, ComboBoxRefsContext, ComboBoxStateContext, } from './helpers';
34
34
  import { ComboBoxDropdown } from './ComboBoxDropdown';
@@ -68,11 +68,10 @@ export var ComboBox = function (props) {
68
68
  var inputRef = React.useRef(null);
69
69
  var menuRef = React.useRef(null);
70
70
  var toggleButtonRef = React.useRef(null);
71
- // Latest value of the onChange prop
72
- var onChangeProp = React.useRef(onChange);
73
- React.useEffect(function () {
74
- onChangeProp.current = onChange;
75
- }, [onChange]);
71
+ var mounted = React.useRef(false);
72
+ var valuePropRef = useLatestRef(valueProp);
73
+ var onChangeProp = useLatestRef(onChange);
74
+ var optionsRef = useLatestRef(options);
76
75
  // Record to store all extra information (e.g. original indexes), where the key is the id of the option
77
76
  var optionsExtraInfoRef = React.useRef({});
78
77
  // Clear the extra info when the options change so that it can be reinitialized below
@@ -91,7 +90,9 @@ export var ComboBox = function (props) {
91
90
  // Reducer where all the component-wide state is stored
92
91
  var _e = React.useReducer(comboBoxReducer, {
93
92
  isOpen: false,
94
- selectedIndex: -1,
93
+ selectedIndex: valueProp
94
+ ? optionsRef.current.findIndex(function (option) { return option.value === valueProp; })
95
+ : -1,
95
96
  focusedIndex: -1,
96
97
  }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
97
98
  React.useLayoutEffect(function () {
@@ -99,7 +100,7 @@ export var ComboBox = function (props) {
99
100
  // When the dropdown opens
100
101
  if (isOpen) {
101
102
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
102
- setFilteredOptions(options); // Reset the filtered list
103
+ setFilteredOptions(optionsRef.current); // Reset the filtered list
103
104
  dispatch(['focus']);
104
105
  }
105
106
  // When the dropdown closes
@@ -108,10 +109,10 @@ export var ComboBox = function (props) {
108
109
  dispatch(['focus']);
109
110
  // Reset the input value
110
111
  setInputValue(selectedIndex != undefined && selectedIndex >= 0
111
- ? (_b = options[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
112
+ ? (_b = optionsRef.current[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
112
113
  : '');
113
114
  }
114
- }, [isOpen, options, selectedIndex]);
115
+ }, [isOpen, optionsRef, selectedIndex]);
115
116
  // Set min-width of menu to be same as input
116
117
  var _g = React.useState(0), minWidth = _g[0], setMinWidth = _g[1];
117
118
  React.useEffect(function () {
@@ -142,14 +143,14 @@ export var ComboBox = function (props) {
142
143
  var value = event.currentTarget.value;
143
144
  setInputValue(value);
144
145
  dispatch(['open']); // reopen when typing
145
- setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(options, value)) !== null && _a !== void 0 ? _a : options.filter(function (option) {
146
+ setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(optionsRef.current, value)) !== null && _a !== void 0 ? _a : optionsRef.current.filter(function (option) {
146
147
  return option.label.toLowerCase().includes(value.toLowerCase());
147
148
  }));
148
149
  if (focusedIndex != -1) {
149
150
  dispatch(['focus', -1]);
150
151
  }
151
152
  (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
152
- }, [filterFunction, focusedIndex, inputProps, options]);
153
+ }, [filterFunction, focusedIndex, inputProps, optionsRef]);
153
154
  // When the value prop changes, update the selectedIndex
154
155
  React.useEffect(function () {
155
156
  dispatch([
@@ -160,14 +161,17 @@ export var ComboBox = function (props) {
160
161
  // Call user-defined onChange when the value actually changes
161
162
  React.useEffect(function () {
162
163
  var _a, _b;
163
- if (selectedIndex != undefined && selectedIndex >= 0) {
164
- var value = (_a = options[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
165
- if (value === valueProp) {
166
- return;
167
- }
168
- (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
164
+ // Prevent user-defined onChange to be called on mount
165
+ if (!mounted.current) {
166
+ mounted.current = true;
167
+ return;
168
+ }
169
+ var currentValue = (_a = optionsRef.current[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
170
+ if (currentValue === valuePropRef.current || selectedIndex === -1) {
171
+ return;
169
172
  }
170
- }, [options, selectedIndex, valueProp]);
173
+ (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, currentValue);
174
+ }, [onChangeProp, optionsRef, selectedIndex, valuePropRef]);
171
175
  var getMenuItem = React.useCallback(function (option, filteredIndex) {
172
176
  var optionId = getOptionId(option, id);
173
177
  var __originalIndex = optionsExtraInfoRef.current[optionId].__originalIndex;
@@ -1,14 +1,16 @@
1
1
  import React from 'react';
2
2
  import { CommonProps } from '../utils';
3
3
  import '@itwin/itwinui-css/css/non-ideal-state.css';
4
- export declare type ErrorPageType = '401' | '403' | '404' | '500' | '502' | '503' | 'generic';
4
+ export declare type ErrorPageType = '300' | '301' | '302' | '303' | '304' | '305' | '307' | '308' | '401' | '403' | '404' | '408' | '500' | '502' | '503' | '504' | 'generic';
5
5
  export declare type ErrorTypeTranslations = {
6
6
  badGateway: string;
7
7
  error: string;
8
8
  forbidden: string;
9
9
  internalServerError: string;
10
+ redirect?: string;
10
11
  pageNotFound: string;
11
12
  serviceUnavailable: string;
13
+ timedOut?: string;
12
14
  unauthorized: string;
13
15
  };
14
16
  export declare type ErrorPageProps = {
@@ -31,6 +31,8 @@ import Svg500 from '@itwin/itwinui-illustrations-react/cjs/illustrations/500';
31
31
  import Svg502 from '@itwin/itwinui-illustrations-react/cjs/illustrations/502';
32
32
  import Svg503 from '@itwin/itwinui-illustrations-react/cjs/illustrations/503';
33
33
  import SvgError from '@itwin/itwinui-illustrations-react/cjs/illustrations/Error';
34
+ import SvgRedirect from '@itwin/itwinui-illustrations-react/cjs/illustrations/Redirect';
35
+ import SvgTimedOut from '@itwin/itwinui-illustrations-react/cjs/illustrations/TimedOut';
34
36
  import React from 'react';
35
37
  import { Button } from '../Buttons/Button';
36
38
  import { useTheme } from '../utils';
@@ -44,9 +46,19 @@ import '@itwin/itwinui-css/css/non-ideal-state.css';
44
46
  export var ErrorPage = function (props) {
45
47
  var errorType = props.errorType, errorName = props.errorName, errorMessage = props.errorMessage, primaryButtonHandle = props.primaryButtonHandle, primaryButtonLabel = props.primaryButtonLabel, secondaryButtonHandle = props.secondaryButtonHandle, secondaryButtonLabel = props.secondaryButtonLabel, translatedErrorMessages = props.translatedErrorMessages, className = props.className, rest = __rest(props, ["errorType", "errorName", "errorMessage", "primaryButtonHandle", "primaryButtonLabel", "secondaryButtonHandle", "secondaryButtonLabel", "translatedErrorMessages", "className"]);
46
48
  useTheme();
47
- var defaultErrorMessages = __assign({ badGateway: 'Bad gateway', error: 'Error', forbidden: 'Forbidden', internalServerError: 'Internal server error', pageNotFound: 'Page not found', serviceUnavailable: 'Service unavailable', unauthorized: 'Unauthorized' }, translatedErrorMessages);
49
+ var defaultErrorMessages = __assign({ badGateway: 'Bad gateway', error: 'Error', forbidden: 'Forbidden', internalServerError: 'Internal server error', redirect: 'Redirect', pageNotFound: 'Page not found', serviceUnavailable: 'Service unavailable', timedOut: 'Timed out', unauthorized: 'Unauthorized' }, translatedErrorMessages);
48
50
  function getErrorIcon() {
49
51
  switch (errorType) {
52
+ case '300':
53
+ case '301':
54
+ case '302':
55
+ case '303':
56
+ case '304':
57
+ case '305':
58
+ case '307':
59
+ case '308': {
60
+ return React.createElement(SvgRedirect, { className: 'iui-non-ideal-state-illustration' });
61
+ }
50
62
  case '401': {
51
63
  return React.createElement(Svg401, { className: 'iui-non-ideal-state-illustration' });
52
64
  }
@@ -56,6 +68,10 @@ export var ErrorPage = function (props) {
56
68
  case '404': {
57
69
  return React.createElement(Svg404, { className: 'iui-non-ideal-state-illustration' });
58
70
  }
71
+ case '408':
72
+ case '504': {
73
+ return React.createElement(SvgTimedOut, { className: 'iui-non-ideal-state-illustration' });
74
+ }
59
75
  case '500': {
60
76
  return React.createElement(Svg500, { className: 'iui-non-ideal-state-illustration' });
61
77
  }
@@ -76,6 +92,16 @@ export var ErrorPage = function (props) {
76
92
  return errorName;
77
93
  }
78
94
  switch (errorType) {
95
+ case '300':
96
+ case '301':
97
+ case '302':
98
+ case '303':
99
+ case '304':
100
+ case '305':
101
+ case '307':
102
+ case '308': {
103
+ return defaultErrorMessages.redirect || '';
104
+ }
79
105
  case '401': {
80
106
  return defaultErrorMessages.unauthorized;
81
107
  }
@@ -85,6 +111,10 @@ export var ErrorPage = function (props) {
85
111
  case '404': {
86
112
  return defaultErrorMessages.pageNotFound;
87
113
  }
114
+ case '408':
115
+ case '504': {
116
+ return defaultErrorMessages.timedOut || '';
117
+ }
88
118
  case '500': {
89
119
  return defaultErrorMessages.internalServerError;
90
120
  }
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { CommonProps } from '../utils';
3
3
  import '@itwin/itwinui-css/css/dialog.css';
4
+ import '@itwin/itwinui-css/css/backdrop.css';
4
5
  export declare type ModalProps = {
5
6
  /**
6
7
  * Flag whether modal should be shown.
@@ -30,6 +30,7 @@ import cx from 'classnames';
30
30
  import SvgClose from '@itwin/itwinui-icons-react/cjs/icons/Close';
31
31
  import { useTheme, getContainer, getDocument, FocusTrap, } from '../utils';
32
32
  import '@itwin/itwinui-css/css/dialog.css';
33
+ import '@itwin/itwinui-css/css/backdrop.css';
33
34
  import { IconButton } from '../Buttons/IconButton';
34
35
  import { CSSTransition } from 'react-transition-group';
35
36
  /**
@@ -112,14 +113,16 @@ export var Modal = function (props) {
112
113
  onClose(event);
113
114
  }
114
115
  };
115
- return !!container ? (ReactDOM.createPortal(React.createElement(CSSTransition, { in: isOpen, classNames: 'iui-dialog-animation', timeout: { exit: 600 }, unmountOnExit: true },
116
+ return !!container ? (ReactDOM.createPortal(React.createElement(React.Fragment, null,
117
+ React.createElement("div", { className: cx('iui-backdrop', { 'iui-backdrop-visible': isOpen }), tabIndex: -1, onKeyDown: handleKeyDown, ref: overlayRef, onMouseDown: handleMouseDown }),
116
118
  React.createElement(FocusTrap, null,
117
- React.createElement("div", __assign({ className: cx('iui-dialog-backdrop', { 'iui-dialog-default': styleType === 'default' }, { 'iui-dialog-full-page': styleType === 'fullPage' }, { 'iui-dialog-visible': isOpen }, className), tabIndex: -1, onKeyDown: handleKeyDown, ref: overlayRef, onMouseDown: handleMouseDown }, rest),
118
- React.createElement("div", { className: 'iui-dialog', id: id, style: style, role: 'dialog', "aria-modal": 'true' },
119
- React.createElement("div", { className: 'iui-dialog-title-bar' },
120
- React.createElement("div", { className: 'iui-dialog-title' }, title),
121
- isDismissible && (React.createElement(IconButton, { size: 'small', styleType: 'borderless', onClick: onClose, "aria-label": 'Close' },
122
- React.createElement(SvgClose, null)))),
123
- children)))), container)) : (React.createElement(React.Fragment, null));
119
+ React.createElement("div", null,
120
+ React.createElement(CSSTransition, { in: isOpen, classNames: 'iui-dialog-animation', timeout: { exit: 600 }, unmountOnExit: true },
121
+ React.createElement("div", __assign({ className: cx('iui-dialog', { 'iui-dialog-default': styleType === 'default' }, { 'iui-dialog-full-page': styleType === 'fullPage' }, { 'iui-dialog-visible': isOpen }, className), id: id, style: style, role: 'dialog', "aria-modal": 'true' }, rest),
122
+ React.createElement("div", { className: 'iui-dialog-title-bar' },
123
+ React.createElement("div", { className: 'iui-dialog-title' }, title),
124
+ isDismissible && (React.createElement(IconButton, { size: 'small', styleType: 'borderless', onClick: onClose, "aria-label": 'Close' },
125
+ React.createElement(SvgClose, null)))),
126
+ children))))), container)) : (React.createElement(React.Fragment, null));
124
127
  };
125
128
  export default Modal;
@@ -182,6 +182,29 @@ export declare type TableProps<T extends Record<string, unknown> = Record<string
182
182
  * @default false
183
183
  */
184
184
  enableColumnReordering?: boolean;
185
+ /**
186
+ * Function that returns index of the row that you want to scroll to.
187
+ *
188
+ * It doesn't work with paginated tables and with lazy-loading.
189
+ * @beta
190
+ * @example
191
+ * <Table
192
+ * scrollToRow={React.useCallback(
193
+ * (rows, data) => rows.findIndex((row) => row.original === data[250]),
194
+ * []
195
+ * )}
196
+ * {...restProps}
197
+ * />
198
+ * @example
199
+ * <Table
200
+ * scrollToRow={React.useCallback(
201
+ * (rows, data) => rows.findIndex((row) => row.original.id === data[250].id),
202
+ * []
203
+ * )}
204
+ * {...restProps}
205
+ * />
206
+ */
207
+ scrollToRow?: (rows: Row<T>[], data: T[]) => number;
185
208
  } & Omit<CommonProps, 'title'>;
186
209
  /**
187
210
  * Table based on [react-table](https://react-table.tanstack.com/docs/api/overview).
@@ -28,7 +28,7 @@ import React from 'react';
28
28
  import cx from 'classnames';
29
29
  import { actions as TableActions, useFlexLayout, useFilters, useRowSelect, useSortBy, useTable, useExpanded, usePagination, useColumnOrder, } from 'react-table';
30
30
  import { ProgressRadial } from '../ProgressIndicators';
31
- import { useTheme, useResizeObserver } from '../utils';
31
+ import { useTheme, useResizeObserver, mergeRefs } from '../utils';
32
32
  import '@itwin/itwinui-css/css/table.css';
33
33
  import SvgSortDown from '@itwin/itwinui-icons-react/cjs/icons/SortDown';
34
34
  import SvgSortUp from '@itwin/itwinui-icons-react/cjs/icons/SortUp';
@@ -36,7 +36,7 @@ import { getCellStyle, getStickyStyle } from './utils';
36
36
  import { TableRowMemoized } from './TableRowMemoized';
37
37
  import { FilterToggle } from './filters';
38
38
  import { customFilterFunctions } from './filters/customFilterFunctions';
39
- import { useExpanderCell, useSelectionCell, useSubRowFiltering, useSubRowSelection, useResizeColumns, useColumnDragAndDrop, useStickyColumns, } from './hooks';
39
+ import { useExpanderCell, useSelectionCell, useSubRowFiltering, useSubRowSelection, useResizeColumns, useColumnDragAndDrop, useScrollToRow, useStickyColumns, } from './hooks';
40
40
  import { onExpandHandler, onFilterHandler, onSelectHandler, onSingleSelectHandler, onTableResizeEnd, onTableResizeStart, } from './actionHandlers';
41
41
  import VirtualScroll from '../utils/components/VirtualScroll';
42
42
  import { SELECTION_CELL_ID } from './columns';
@@ -234,6 +234,7 @@ export var Table = function (props) {
234
234
  state.pageIndex,
235
235
  state.pageSize,
236
236
  ]);
237
+ var _r = useScrollToRow(__assign(__assign({}, props), { page: page })), scrollToIndex = _r.scrollToIndex, tableRowRef = _r.tableRowRef;
237
238
  var columnRefs = React.useRef({});
238
239
  var previousTableWidth = React.useRef(0);
239
240
  var onTableResize = React.useCallback(function (_a) {
@@ -269,22 +270,27 @@ export var Table = function (props) {
269
270
  });
270
271
  var headerRef = React.useRef(null);
271
272
  var bodyRef = React.useRef(null);
273
+ // Using `useState` to rerender rows when table body ref is available
274
+ var _s = React.useState(null), bodyRefState = _s[0], setBodyRefState = _s[1];
272
275
  var getPreparedRow = React.useCallback(function (index) {
273
276
  var row = page[index];
274
277
  prepareRow(row);
275
- return (React.createElement(TableRowMemoized, { row: row, rowProps: rowProps, isLast: index === page.length - 1, onRowInViewport: onRowInViewportRef, onBottomReached: onBottomReachedRef, intersectionMargin: intersectionMargin, state: state, key: row.getRowProps().key, onClick: onRowClickHandler, subComponent: subComponent, isDisabled: !!(isRowDisabled === null || isRowDisabled === void 0 ? void 0 : isRowDisabled(row.original)), tableHasSubRows: hasAnySubRows, tableInstance: instance, expanderCell: expanderCell }));
278
+ return (React.createElement(TableRowMemoized, { row: row, rowProps: rowProps, isLast: index === page.length - 1, onRowInViewport: onRowInViewportRef, onBottomReached: onBottomReachedRef, intersectionMargin: intersectionMargin, state: state, key: row.getRowProps().key, onClick: onRowClickHandler, subComponent: subComponent, isDisabled: !!(isRowDisabled === null || isRowDisabled === void 0 ? void 0 : isRowDisabled(row.original)), tableHasSubRows: hasAnySubRows, tableInstance: instance, expanderCell: expanderCell, bodyRef: bodyRefState, tableRowRef: enableVirtualization ? undefined : tableRowRef(row) }));
276
279
  }, [
277
280
  page,
278
- expanderCell,
279
- hasAnySubRows,
280
- instance,
281
- intersectionMargin,
282
- isRowDisabled,
283
- onRowClickHandler,
284
281
  prepareRow,
285
282
  rowProps,
283
+ intersectionMargin,
286
284
  state,
285
+ onRowClickHandler,
287
286
  subComponent,
287
+ isRowDisabled,
288
+ hasAnySubRows,
289
+ instance,
290
+ expanderCell,
291
+ bodyRefState,
292
+ enableVirtualization,
293
+ tableRowRef,
288
294
  ]);
289
295
  var virtualizedItemRenderer = React.useCallback(function (index) { return getPreparedRow(index); }, [getPreparedRow]);
290
296
  var updateStickyState = function () {
@@ -360,13 +366,13 @@ export var Table = function (props) {
360
366
  'iui-zebra-striping': styleType === 'zebra-rows',
361
367
  }),
362
368
  style: { outline: 0 },
363
- }), { ref: bodyRef, onScroll: function () {
369
+ }), { ref: mergeRefs(bodyRef, setBodyRefState), onScroll: function () {
364
370
  if (headerRef.current && bodyRef.current) {
365
371
  headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
366
372
  updateStickyState();
367
373
  }
368
374
  }, tabIndex: -1 }),
369
- data.length !== 0 && (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualScroll, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer })) : (page.map(function (_, index) { return getPreparedRow(index); })))),
375
+ data.length !== 0 && (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualScroll, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer, scrollToIndex: scrollToIndex })) : (page.map(function (_, index) { return getPreparedRow(index); })))),
370
376
  isLoading && data.length === 0 && (React.createElement("div", { className: 'iui-table-empty' },
371
377
  React.createElement(ProgressRadial, { indeterminate: true }))),
372
378
  isLoading && data.length !== 0 && (React.createElement("div", { className: 'iui-row' },
@@ -20,6 +20,8 @@ export declare const TableRow: <T extends Record<string, unknown>>(props: {
20
20
  tableHasSubRows: boolean;
21
21
  tableInstance: TableInstance<T>;
22
22
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
23
+ bodyRef: HTMLDivElement | null;
24
+ tableRowRef?: React.Ref<HTMLDivElement> | undefined;
23
25
  }) => JSX.Element;
24
26
  export declare const TableRowMemoized: <T extends Record<string, unknown>>(props: {
25
27
  row: Row<T>;
@@ -35,4 +37,6 @@ export declare const TableRowMemoized: <T extends Record<string, unknown>>(props
35
37
  tableHasSubRows: boolean;
36
38
  tableInstance: TableInstance<T>;
37
39
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
40
+ bodyRef: HTMLDivElement | null;
41
+ tableRowRef?: React.Ref<HTMLDivElement> | undefined;
38
42
  }) => JSX.Element;