@itwin/itwinui-react 1.41.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 (38) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/cjs/core/ComboBox/ComboBox.js +15 -14
  3. package/cjs/core/Table/Table.d.ts +23 -0
  4. package/cjs/core/Table/Table.js +6 -3
  5. package/cjs/core/Table/TableRowMemoized.d.ts +2 -0
  6. package/cjs/core/Table/TableRowMemoized.js +2 -2
  7. package/cjs/core/Table/hooks/index.d.ts +1 -0
  8. package/cjs/core/Table/hooks/index.js +3 -1
  9. package/cjs/core/Table/hooks/useScrollToRow.d.ts +11 -0
  10. package/cjs/core/Table/hooks/useScrollToRow.js +49 -0
  11. package/cjs/core/Tree/Tree.d.ts +9 -0
  12. package/cjs/core/Tree/Tree.js +67 -19
  13. package/cjs/core/Tree/TreeContext.d.ts +4 -0
  14. package/cjs/core/Tree/TreeNode.js +8 -9
  15. package/cjs/core/Typography/Small/Small.js +1 -1
  16. package/cjs/core/utils/hooks/index.d.ts +1 -0
  17. package/cjs/core/utils/hooks/index.js +1 -0
  18. package/cjs/core/utils/hooks/useLatestRef.d.ts +9 -0
  19. package/cjs/core/utils/hooks/useLatestRef.js +26 -0
  20. package/esm/core/ComboBox/ComboBox.js +16 -15
  21. package/esm/core/Table/Table.d.ts +23 -0
  22. package/esm/core/Table/Table.js +7 -4
  23. package/esm/core/Table/TableRowMemoized.d.ts +2 -0
  24. package/esm/core/Table/TableRowMemoized.js +2 -2
  25. package/esm/core/Table/hooks/index.d.ts +1 -0
  26. package/esm/core/Table/hooks/index.js +1 -0
  27. package/esm/core/Table/hooks/useScrollToRow.d.ts +11 -0
  28. package/esm/core/Table/hooks/useScrollToRow.js +42 -0
  29. package/esm/core/Tree/Tree.d.ts +9 -0
  30. package/esm/core/Tree/Tree.js +68 -20
  31. package/esm/core/Tree/TreeContext.d.ts +4 -0
  32. package/esm/core/Tree/TreeNode.js +8 -9
  33. package/esm/core/Typography/Small/Small.js +1 -1
  34. package/esm/core/utils/hooks/index.d.ts +1 -0
  35. package/esm/core/utils/hooks/index.js +1 -0
  36. package/esm/core/utils/hooks/useLatestRef.d.ts +9 -0
  37. package/esm/core/utils/hooks/useLatestRef.js +19 -0
  38. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.42.0](https://www.github.com/iTwin/iTwinUI-react/compare/v1.41.0...v1.42.0) (2022-07-26)
4
+
5
+ ### Fixes
6
+
7
+ * **ComboBox:** Prevent infinite loop when `options` change ([#738](https://www.github.com/iTwin/iTwinUI-react/issues/738)) ([7788f45](https://www.github.com/iTwin/iTwinUI-react/commit/7788f451fcbf9dd7959d3fa727c0cdee3485bbcd))
8
+ * **Small:** Use `small` element instead of `p` ([#735](https://www.github.com/iTwin/iTwinUI-react/issues/735)) ([c59f213](https://www.github.com/iTwin/iTwinUI-react/commit/c59f21326e049bce50e24c729a46482b74ef63c3))
9
+
10
+
11
+ ### What's new
12
+
13
+ * **Table:** Scroll table to selected item ([#689](https://www.github.com/iTwin/iTwinUI-react/issues/689)) ([afa947b](https://www.github.com/iTwin/iTwinUI-react/commit/afa947b15411305c1bfc57b239d916fc599acfbe))
14
+ * **Tree:** Virtualization ([#713](https://www.github.com/iTwin/iTwinUI-react/issues/713)) ([d4d3575](https://www.github.com/iTwin/iTwinUI-react/commit/d4d35758fa0c8f00811f387cc8453b82e9773c85))
15
+
3
16
  ## [1.41.0](https://www.github.com/iTwin/iTwinUI-react/compare/v1.40.1...v1.41.0) (2022-07-13)
4
17
 
5
18
  ### What's new
@@ -75,11 +75,9 @@ var ComboBox = function (props) {
75
75
  var menuRef = react_1.default.useRef(null);
76
76
  var toggleButtonRef = react_1.default.useRef(null);
77
77
  var mounted = react_1.default.useRef(false);
78
- // Latest value of the onChange prop
79
- var onChangeProp = react_1.default.useRef(onChange);
80
- react_1.default.useEffect(function () {
81
- onChangeProp.current = onChange;
82
- }, [onChange]);
78
+ var valuePropRef = (0, utils_1.useLatestRef)(valueProp);
79
+ var onChangeProp = (0, utils_1.useLatestRef)(onChange);
80
+ var optionsRef = (0, utils_1.useLatestRef)(options);
83
81
  // Record to store all extra information (e.g. original indexes), where the key is the id of the option
84
82
  var optionsExtraInfoRef = react_1.default.useRef({});
85
83
  // Clear the extra info when the options change so that it can be reinitialized below
@@ -99,7 +97,7 @@ var ComboBox = function (props) {
99
97
  var _e = react_1.default.useReducer(helpers_1.comboBoxReducer, {
100
98
  isOpen: false,
101
99
  selectedIndex: valueProp
102
- ? options.findIndex(function (option) { return option.value === valueProp; })
100
+ ? optionsRef.current.findIndex(function (option) { return option.value === valueProp; })
103
101
  : -1,
104
102
  focusedIndex: -1,
105
103
  }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
@@ -108,7 +106,7 @@ var ComboBox = function (props) {
108
106
  // When the dropdown opens
109
107
  if (isOpen) {
110
108
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
111
- setFilteredOptions(options); // Reset the filtered list
109
+ setFilteredOptions(optionsRef.current); // Reset the filtered list
112
110
  dispatch(['focus']);
113
111
  }
114
112
  // When the dropdown closes
@@ -117,10 +115,10 @@ var ComboBox = function (props) {
117
115
  dispatch(['focus']);
118
116
  // Reset the input value
119
117
  setInputValue(selectedIndex != undefined && selectedIndex >= 0
120
- ? (_b = options[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
118
+ ? (_b = optionsRef.current[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
121
119
  : '');
122
120
  }
123
- }, [isOpen, options, selectedIndex]);
121
+ }, [isOpen, optionsRef, selectedIndex]);
124
122
  // Set min-width of menu to be same as input
125
123
  var _g = react_1.default.useState(0), minWidth = _g[0], setMinWidth = _g[1];
126
124
  react_1.default.useEffect(function () {
@@ -151,14 +149,14 @@ var ComboBox = function (props) {
151
149
  var value = event.currentTarget.value;
152
150
  setInputValue(value);
153
151
  dispatch(['open']); // reopen when typing
154
- setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(options, value)) !== null && _a !== void 0 ? _a : options.filter(function (option) {
152
+ setFilteredOptions((_a = filterFunction === null || filterFunction === void 0 ? void 0 : filterFunction(optionsRef.current, value)) !== null && _a !== void 0 ? _a : optionsRef.current.filter(function (option) {
155
153
  return option.label.toLowerCase().includes(value.toLowerCase());
156
154
  }));
157
155
  if (focusedIndex != -1) {
158
156
  dispatch(['focus', -1]);
159
157
  }
160
158
  (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
161
- }, [filterFunction, focusedIndex, inputProps, options]);
159
+ }, [filterFunction, focusedIndex, inputProps, optionsRef]);
162
160
  // When the value prop changes, update the selectedIndex
163
161
  react_1.default.useEffect(function () {
164
162
  dispatch([
@@ -174,9 +172,12 @@ var ComboBox = function (props) {
174
172
  mounted.current = true;
175
173
  return;
176
174
  }
177
- var value = (_a = options[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
178
- (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
179
- }, [options, selectedIndex]);
175
+ var currentValue = (_a = optionsRef.current[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
176
+ if (currentValue === valuePropRef.current || selectedIndex === -1) {
177
+ return;
178
+ }
179
+ (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, currentValue);
180
+ }, [onChangeProp, optionsRef, selectedIndex, valuePropRef]);
180
181
  var getMenuItem = react_1.default.useCallback(function (option, filteredIndex) {
181
182
  var optionId = getOptionId(option, id);
182
183
  var __originalIndex = optionsExtraInfoRef.current[optionId].__originalIndex;
@@ -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).
@@ -240,6 +240,7 @@ var Table = function (props) {
240
240
  state.pageIndex,
241
241
  state.pageSize,
242
242
  ]);
243
+ var _r = (0, hooks_1.useScrollToRow)(__assign(__assign({}, props), { page: page })), scrollToIndex = _r.scrollToIndex, tableRowRef = _r.tableRowRef;
243
244
  var columnRefs = react_1.default.useRef({});
244
245
  var previousTableWidth = react_1.default.useRef(0);
245
246
  var onTableResize = react_1.default.useCallback(function (_a) {
@@ -276,11 +277,11 @@ var Table = function (props) {
276
277
  var headerRef = react_1.default.useRef(null);
277
278
  var bodyRef = react_1.default.useRef(null);
278
279
  // Using `useState` to rerender rows when table body ref is available
279
- var _r = react_1.default.useState(null), bodyRefState = _r[0], setBodyRefState = _r[1];
280
+ var _s = react_1.default.useState(null), bodyRefState = _s[0], setBodyRefState = _s[1];
280
281
  var getPreparedRow = react_1.default.useCallback(function (index) {
281
282
  var row = page[index];
282
283
  prepareRow(row);
283
- return (react_1.default.createElement(TableRowMemoized_1.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 }));
284
+ return (react_1.default.createElement(TableRowMemoized_1.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) }));
284
285
  }, [
285
286
  page,
286
287
  prepareRow,
@@ -294,6 +295,8 @@ var Table = function (props) {
294
295
  instance,
295
296
  expanderCell,
296
297
  bodyRefState,
298
+ enableVirtualization,
299
+ tableRowRef,
297
300
  ]);
298
301
  var virtualizedItemRenderer = react_1.default.useCallback(function (index) { return getPreparedRow(index); }, [getPreparedRow]);
299
302
  var updateStickyState = function () {
@@ -375,7 +378,7 @@ var Table = function (props) {
375
378
  updateStickyState();
376
379
  }
377
380
  }, tabIndex: -1 }),
378
- data.length !== 0 && (react_1.default.createElement(react_1.default.Fragment, null, enableVirtualization ? (react_1.default.createElement(VirtualScroll_1.default, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer })) : (page.map(function (_, index) { return getPreparedRow(index); })))),
381
+ data.length !== 0 && (react_1.default.createElement(react_1.default.Fragment, null, enableVirtualization ? (react_1.default.createElement(VirtualScroll_1.default, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer, scrollToIndex: scrollToIndex })) : (page.map(function (_, index) { return getPreparedRow(index); })))),
379
382
  isLoading && data.length === 0 && (react_1.default.createElement("div", { className: 'iui-table-empty' },
380
383
  react_1.default.createElement(ProgressIndicators_1.ProgressRadial, { indeterminate: true }))),
381
384
  isLoading && data.length !== 0 && (react_1.default.createElement("div", { className: 'iui-row' },
@@ -21,6 +21,7 @@ export declare const TableRow: <T extends Record<string, unknown>>(props: {
21
21
  tableInstance: TableInstance<T>;
22
22
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
23
23
  bodyRef: HTMLDivElement | null;
24
+ tableRowRef?: React.Ref<HTMLDivElement> | undefined;
24
25
  }) => JSX.Element;
25
26
  export declare const TableRowMemoized: <T extends Record<string, unknown>>(props: {
26
27
  row: Row<T>;
@@ -37,4 +38,5 @@ export declare const TableRowMemoized: <T extends Record<string, unknown>>(props
37
38
  tableInstance: TableInstance<T>;
38
39
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
39
40
  bodyRef: HTMLDivElement | null;
41
+ tableRowRef?: React.Ref<HTMLDivElement> | undefined;
40
42
  }) => JSX.Element;
@@ -30,7 +30,7 @@ var TableCell_1 = require("./TableCell");
30
30
  * When adding new features check whether it changes state that affects row. If it does then add equality check to `React.memo`.
31
31
  */
32
32
  var TableRow = function (props) {
33
- var row = props.row, rowProps = props.rowProps, isLast = props.isLast, onRowInViewport = props.onRowInViewport, onBottomReached = props.onBottomReached, intersectionMargin = props.intersectionMargin, onClick = props.onClick, subComponent = props.subComponent, isDisabled = props.isDisabled, tableHasSubRows = props.tableHasSubRows, tableInstance = props.tableInstance, expanderCell = props.expanderCell, bodyRef = props.bodyRef;
33
+ var row = props.row, rowProps = props.rowProps, isLast = props.isLast, onRowInViewport = props.onRowInViewport, onBottomReached = props.onBottomReached, intersectionMargin = props.intersectionMargin, onClick = props.onClick, subComponent = props.subComponent, isDisabled = props.isDisabled, tableHasSubRows = props.tableHasSubRows, tableInstance = props.tableInstance, expanderCell = props.expanderCell, bodyRef = props.bodyRef, tableRowRef = props.tableRowRef;
34
34
  var onIntersect = react_1.default.useCallback(function () {
35
35
  var _a, _b;
36
36
  (_a = onRowInViewport.current) === null || _a === void 0 ? void 0 : _a.call(onRowInViewport, row.original);
@@ -58,7 +58,7 @@ var TableRow = function (props) {
58
58
  'iui-disabled': isDisabled,
59
59
  }, userRowProps === null || userRowProps === void 0 ? void 0 : userRowProps.className),
60
60
  });
61
- var refs = (0, utils_1.useMergedRefs)(intersectionRef, mergedProps.ref);
61
+ var refs = (0, utils_1.useMergedRefs)(intersectionRef, mergedProps.ref, tableRowRef);
62
62
  return (react_1.default.createElement(react_1.default.Fragment, null,
63
63
  react_1.default.createElement("div", __assign({}, mergedProps, { ref: refs, onClick: function (event) {
64
64
  var _a;
@@ -4,4 +4,5 @@ export { useSubRowFiltering } from './useSubRowFiltering';
4
4
  export { useSubRowSelection } from './useSubRowSelection';
5
5
  export { useResizeColumns } from './useResizeColumns';
6
6
  export { useColumnDragAndDrop } from './useColumnDragAndDrop';
7
+ export { useScrollToRow } from './useScrollToRow';
7
8
  export { useStickyColumns } from './useStickyColumns';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useStickyColumns = exports.useColumnDragAndDrop = exports.useResizeColumns = exports.useSubRowSelection = exports.useSubRowFiltering = exports.useSelectionCell = exports.useExpanderCell = void 0;
3
+ exports.useStickyColumns = exports.useScrollToRow = exports.useColumnDragAndDrop = exports.useResizeColumns = exports.useSubRowSelection = exports.useSubRowFiltering = exports.useSelectionCell = exports.useExpanderCell = void 0;
4
4
  /*---------------------------------------------------------------------------------------------
5
5
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
6
6
  * See LICENSE.md in the project root for license terms and full copyright notice.
@@ -17,5 +17,7 @@ var useResizeColumns_1 = require("./useResizeColumns");
17
17
  Object.defineProperty(exports, "useResizeColumns", { enumerable: true, get: function () { return useResizeColumns_1.useResizeColumns; } });
18
18
  var useColumnDragAndDrop_1 = require("./useColumnDragAndDrop");
19
19
  Object.defineProperty(exports, "useColumnDragAndDrop", { enumerable: true, get: function () { return useColumnDragAndDrop_1.useColumnDragAndDrop; } });
20
+ var useScrollToRow_1 = require("./useScrollToRow");
21
+ Object.defineProperty(exports, "useScrollToRow", { enumerable: true, get: function () { return useScrollToRow_1.useScrollToRow; } });
20
22
  var useStickyColumns_1 = require("./useStickyColumns");
21
23
  Object.defineProperty(exports, "useStickyColumns", { enumerable: true, get: function () { return useStickyColumns_1.useStickyColumns; } });
@@ -0,0 +1,11 @@
1
+ import type { Row } from 'react-table';
2
+ import { TableProps } from '../Table';
3
+ declare type ScrollToRow<T extends Record<string, unknown>> = {
4
+ scrollToIndex: number | undefined;
5
+ tableRowRef: (row: Row<T>) => (element: HTMLDivElement) => void;
6
+ };
7
+ declare type ScrollToRowProps<T extends Record<string, unknown>> = TableProps<T> & {
8
+ page: Row<T>[];
9
+ };
10
+ export declare function useScrollToRow<T extends Record<string, unknown>>({ data, enableVirtualization, page, paginatorRenderer, scrollToRow, onBottomReached, }: ScrollToRowProps<T>): ScrollToRow<T>;
11
+ export {};
@@ -0,0 +1,49 @@
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.useScrollToRow = 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
+ function useScrollToRow(_a) {
13
+ var data = _a.data, enableVirtualization = _a.enableVirtualization, page = _a.page, paginatorRenderer = _a.paginatorRenderer, scrollToRow = _a.scrollToRow, onBottomReached = _a.onBottomReached;
14
+ var rowRefs = react_1.default.useRef({});
15
+ // Refs prevents from having `page` and `data` as dependencies
16
+ // therefore we avoid unnecessary scroll to row.
17
+ var pageRef = react_1.default.useRef(page);
18
+ pageRef.current = page;
19
+ var dataRef = react_1.default.useRef(data);
20
+ dataRef.current = data;
21
+ // For virtualized tables, all we need to do is pass the index of the item
22
+ // to the VirtualScroll component
23
+ var scrollToIndex = react_1.default.useMemo(function () {
24
+ if (!scrollToRow || paginatorRenderer || onBottomReached) {
25
+ return undefined;
26
+ }
27
+ var index = scrollToRow(pageRef.current, dataRef.current);
28
+ return index < 0 ? undefined : index;
29
+ }, [onBottomReached, paginatorRenderer, scrollToRow]);
30
+ // For non-virtualized tables, we need to add a ref to each row
31
+ // and scroll to the element
32
+ react_1.default.useEffect(function () {
33
+ var _a;
34
+ if (enableVirtualization ||
35
+ scrollToIndex === undefined ||
36
+ scrollToIndex === null ||
37
+ scrollToIndex < 0) {
38
+ return;
39
+ }
40
+ (_a = rowRefs.current[pageRef.current[scrollToIndex].id]) === null || _a === void 0 ? void 0 : _a.scrollIntoView();
41
+ }, [enableVirtualization, scrollToIndex]);
42
+ var tableRowRef = react_1.default.useCallback(function (row) {
43
+ return function (element) {
44
+ rowRefs.current[row.id] = element;
45
+ };
46
+ }, []);
47
+ return { scrollToIndex: scrollToIndex, tableRowRef: tableRowRef };
48
+ }
49
+ exports.useScrollToRow = useScrollToRow;
@@ -68,6 +68,15 @@ export declare type TreeProps<T> = {
68
68
  * }, [expandedNodes]);
69
69
  */
70
70
  getNode: (node: T) => NodeData<T>;
71
+ /**
72
+ * Virtualization is used to have a better performance with a lot of nodes.
73
+ *
74
+ * When enabled, Tree DOM structure will change - it will have a wrapper div
75
+ * to which `className` and `style` will be applied.
76
+ * @default false
77
+ * @beta
78
+ */
79
+ enableVirtualization?: boolean;
71
80
  } & Omit<CommonProps, 'title'>;
72
81
  /**
73
82
  * Tree component used to display a hierarchical structure of `TreeNodes`.
@@ -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;
@@ -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';
@@ -69,11 +69,9 @@ export var ComboBox = function (props) {
69
69
  var menuRef = React.useRef(null);
70
70
  var toggleButtonRef = React.useRef(null);
71
71
  var mounted = React.useRef(false);
72
- // Latest value of the onChange prop
73
- var onChangeProp = React.useRef(onChange);
74
- React.useEffect(function () {
75
- onChangeProp.current = onChange;
76
- }, [onChange]);
72
+ var valuePropRef = useLatestRef(valueProp);
73
+ var onChangeProp = useLatestRef(onChange);
74
+ var optionsRef = useLatestRef(options);
77
75
  // Record to store all extra information (e.g. original indexes), where the key is the id of the option
78
76
  var optionsExtraInfoRef = React.useRef({});
79
77
  // Clear the extra info when the options change so that it can be reinitialized below
@@ -93,7 +91,7 @@ export var ComboBox = function (props) {
93
91
  var _e = React.useReducer(comboBoxReducer, {
94
92
  isOpen: false,
95
93
  selectedIndex: valueProp
96
- ? options.findIndex(function (option) { return option.value === valueProp; })
94
+ ? optionsRef.current.findIndex(function (option) { return option.value === valueProp; })
97
95
  : -1,
98
96
  focusedIndex: -1,
99
97
  }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
@@ -102,7 +100,7 @@ export var ComboBox = function (props) {
102
100
  // When the dropdown opens
103
101
  if (isOpen) {
104
102
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
105
- setFilteredOptions(options); // Reset the filtered list
103
+ setFilteredOptions(optionsRef.current); // Reset the filtered list
106
104
  dispatch(['focus']);
107
105
  }
108
106
  // When the dropdown closes
@@ -111,10 +109,10 @@ export var ComboBox = function (props) {
111
109
  dispatch(['focus']);
112
110
  // Reset the input value
113
111
  setInputValue(selectedIndex != undefined && selectedIndex >= 0
114
- ? (_b = options[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
112
+ ? (_b = optionsRef.current[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
115
113
  : '');
116
114
  }
117
- }, [isOpen, options, selectedIndex]);
115
+ }, [isOpen, optionsRef, selectedIndex]);
118
116
  // Set min-width of menu to be same as input
119
117
  var _g = React.useState(0), minWidth = _g[0], setMinWidth = _g[1];
120
118
  React.useEffect(function () {
@@ -145,14 +143,14 @@ export var ComboBox = function (props) {
145
143
  var value = event.currentTarget.value;
146
144
  setInputValue(value);
147
145
  dispatch(['open']); // reopen when typing
148
- 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) {
149
147
  return option.label.toLowerCase().includes(value.toLowerCase());
150
148
  }));
151
149
  if (focusedIndex != -1) {
152
150
  dispatch(['focus', -1]);
153
151
  }
154
152
  (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
155
- }, [filterFunction, focusedIndex, inputProps, options]);
153
+ }, [filterFunction, focusedIndex, inputProps, optionsRef]);
156
154
  // When the value prop changes, update the selectedIndex
157
155
  React.useEffect(function () {
158
156
  dispatch([
@@ -168,9 +166,12 @@ export var ComboBox = function (props) {
168
166
  mounted.current = true;
169
167
  return;
170
168
  }
171
- var value = (_a = options[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
172
- (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
173
- }, [options, selectedIndex]);
169
+ var currentValue = (_a = optionsRef.current[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
170
+ if (currentValue === valuePropRef.current || selectedIndex === -1) {
171
+ return;
172
+ }
173
+ (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, currentValue);
174
+ }, [onChangeProp, optionsRef, selectedIndex, valuePropRef]);
174
175
  var getMenuItem = React.useCallback(function (option, filteredIndex) {
175
176
  var optionId = getOptionId(option, id);
176
177
  var __originalIndex = optionsExtraInfoRef.current[optionId].__originalIndex;
@@ -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).
@@ -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) {
@@ -270,11 +271,11 @@ export var Table = function (props) {
270
271
  var headerRef = React.useRef(null);
271
272
  var bodyRef = React.useRef(null);
272
273
  // Using `useState` to rerender rows when table body ref is available
273
- var _r = React.useState(null), bodyRefState = _r[0], setBodyRefState = _r[1];
274
+ var _s = React.useState(null), bodyRefState = _s[0], setBodyRefState = _s[1];
274
275
  var getPreparedRow = React.useCallback(function (index) {
275
276
  var row = page[index];
276
277
  prepareRow(row);
277
- 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 }));
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) }));
278
279
  }, [
279
280
  page,
280
281
  prepareRow,
@@ -288,6 +289,8 @@ export var Table = function (props) {
288
289
  instance,
289
290
  expanderCell,
290
291
  bodyRefState,
292
+ enableVirtualization,
293
+ tableRowRef,
291
294
  ]);
292
295
  var virtualizedItemRenderer = React.useCallback(function (index) { return getPreparedRow(index); }, [getPreparedRow]);
293
296
  var updateStickyState = function () {
@@ -369,7 +372,7 @@ export var Table = function (props) {
369
372
  updateStickyState();
370
373
  }
371
374
  }, tabIndex: -1 }),
372
- 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); })))),
373
376
  isLoading && data.length === 0 && (React.createElement("div", { className: 'iui-table-empty' },
374
377
  React.createElement(ProgressRadial, { indeterminate: true }))),
375
378
  isLoading && data.length !== 0 && (React.createElement("div", { className: 'iui-row' },
@@ -21,6 +21,7 @@ export declare const TableRow: <T extends Record<string, unknown>>(props: {
21
21
  tableInstance: TableInstance<T>;
22
22
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
23
23
  bodyRef: HTMLDivElement | null;
24
+ tableRowRef?: React.Ref<HTMLDivElement> | undefined;
24
25
  }) => JSX.Element;
25
26
  export declare const TableRowMemoized: <T extends Record<string, unknown>>(props: {
26
27
  row: Row<T>;
@@ -37,4 +38,5 @@ export declare const TableRowMemoized: <T extends Record<string, unknown>>(props
37
38
  tableInstance: TableInstance<T>;
38
39
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
39
40
  bodyRef: HTMLDivElement | null;
41
+ tableRowRef?: React.Ref<HTMLDivElement> | undefined;
40
42
  }) => JSX.Element;
@@ -24,7 +24,7 @@ import { TableCell } from './TableCell';
24
24
  * When adding new features check whether it changes state that affects row. If it does then add equality check to `React.memo`.
25
25
  */
26
26
  export var TableRow = function (props) {
27
- var row = props.row, rowProps = props.rowProps, isLast = props.isLast, onRowInViewport = props.onRowInViewport, onBottomReached = props.onBottomReached, intersectionMargin = props.intersectionMargin, onClick = props.onClick, subComponent = props.subComponent, isDisabled = props.isDisabled, tableHasSubRows = props.tableHasSubRows, tableInstance = props.tableInstance, expanderCell = props.expanderCell, bodyRef = props.bodyRef;
27
+ var row = props.row, rowProps = props.rowProps, isLast = props.isLast, onRowInViewport = props.onRowInViewport, onBottomReached = props.onBottomReached, intersectionMargin = props.intersectionMargin, onClick = props.onClick, subComponent = props.subComponent, isDisabled = props.isDisabled, tableHasSubRows = props.tableHasSubRows, tableInstance = props.tableInstance, expanderCell = props.expanderCell, bodyRef = props.bodyRef, tableRowRef = props.tableRowRef;
28
28
  var onIntersect = React.useCallback(function () {
29
29
  var _a, _b;
30
30
  (_a = onRowInViewport.current) === null || _a === void 0 ? void 0 : _a.call(onRowInViewport, row.original);
@@ -52,7 +52,7 @@ export var TableRow = function (props) {
52
52
  'iui-disabled': isDisabled,
53
53
  }, userRowProps === null || userRowProps === void 0 ? void 0 : userRowProps.className),
54
54
  });
55
- var refs = useMergedRefs(intersectionRef, mergedProps.ref);
55
+ var refs = useMergedRefs(intersectionRef, mergedProps.ref, tableRowRef);
56
56
  return (React.createElement(React.Fragment, null,
57
57
  React.createElement("div", __assign({}, mergedProps, { ref: refs, onClick: function (event) {
58
58
  var _a;
@@ -4,4 +4,5 @@ export { useSubRowFiltering } from './useSubRowFiltering';
4
4
  export { useSubRowSelection } from './useSubRowSelection';
5
5
  export { useResizeColumns } from './useResizeColumns';
6
6
  export { useColumnDragAndDrop } from './useColumnDragAndDrop';
7
+ export { useScrollToRow } from './useScrollToRow';
7
8
  export { useStickyColumns } from './useStickyColumns';
@@ -8,4 +8,5 @@ export { useSubRowFiltering } from './useSubRowFiltering';
8
8
  export { useSubRowSelection } from './useSubRowSelection';
9
9
  export { useResizeColumns } from './useResizeColumns';
10
10
  export { useColumnDragAndDrop } from './useColumnDragAndDrop';
11
+ export { useScrollToRow } from './useScrollToRow';
11
12
  export { useStickyColumns } from './useStickyColumns';
@@ -0,0 +1,11 @@
1
+ import type { Row } from 'react-table';
2
+ import { TableProps } from '../Table';
3
+ declare type ScrollToRow<T extends Record<string, unknown>> = {
4
+ scrollToIndex: number | undefined;
5
+ tableRowRef: (row: Row<T>) => (element: HTMLDivElement) => void;
6
+ };
7
+ declare type ScrollToRowProps<T extends Record<string, unknown>> = TableProps<T> & {
8
+ page: Row<T>[];
9
+ };
10
+ export declare function useScrollToRow<T extends Record<string, unknown>>({ data, enableVirtualization, page, paginatorRenderer, scrollToRow, onBottomReached, }: ScrollToRowProps<T>): ScrollToRow<T>;
11
+ export {};
@@ -0,0 +1,42 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import React from 'react';
6
+ export function useScrollToRow(_a) {
7
+ var data = _a.data, enableVirtualization = _a.enableVirtualization, page = _a.page, paginatorRenderer = _a.paginatorRenderer, scrollToRow = _a.scrollToRow, onBottomReached = _a.onBottomReached;
8
+ var rowRefs = React.useRef({});
9
+ // Refs prevents from having `page` and `data` as dependencies
10
+ // therefore we avoid unnecessary scroll to row.
11
+ var pageRef = React.useRef(page);
12
+ pageRef.current = page;
13
+ var dataRef = React.useRef(data);
14
+ dataRef.current = data;
15
+ // For virtualized tables, all we need to do is pass the index of the item
16
+ // to the VirtualScroll component
17
+ var scrollToIndex = React.useMemo(function () {
18
+ if (!scrollToRow || paginatorRenderer || onBottomReached) {
19
+ return undefined;
20
+ }
21
+ var index = scrollToRow(pageRef.current, dataRef.current);
22
+ return index < 0 ? undefined : index;
23
+ }, [onBottomReached, paginatorRenderer, scrollToRow]);
24
+ // For non-virtualized tables, we need to add a ref to each row
25
+ // and scroll to the element
26
+ React.useEffect(function () {
27
+ var _a;
28
+ if (enableVirtualization ||
29
+ scrollToIndex === undefined ||
30
+ scrollToIndex === null ||
31
+ scrollToIndex < 0) {
32
+ return;
33
+ }
34
+ (_a = rowRefs.current[pageRef.current[scrollToIndex].id]) === null || _a === void 0 ? void 0 : _a.scrollIntoView();
35
+ }, [enableVirtualization, scrollToIndex]);
36
+ var tableRowRef = React.useCallback(function (row) {
37
+ return function (element) {
38
+ rowRefs.current[row.id] = element;
39
+ };
40
+ }, []);
41
+ return { scrollToIndex: scrollToIndex, tableRowRef: tableRowRef };
42
+ }
@@ -68,6 +68,15 @@ export declare type TreeProps<T> = {
68
68
  * }, [expandedNodes]);
69
69
  */
70
70
  getNode: (node: T) => NodeData<T>;
71
+ /**
72
+ * Virtualization is used to have a better performance with a lot of nodes.
73
+ *
74
+ * When enabled, Tree DOM structure will change - it will have a wrapper div
75
+ * to which `className` and `style` will be applied.
76
+ * @default false
77
+ * @beta
78
+ */
79
+ enableVirtualization?: boolean;
71
80
  } & Omit<CommonProps, 'title'>;
72
81
  /**
73
82
  * Tree component used to display a hierarchical structure of `TreeNodes`.
@@ -25,7 +25,7 @@ var __rest = (this && this.__rest) || function (s, e) {
25
25
  * See LICENSE.md in the project root for license terms and full copyright notice.
26
26
  *--------------------------------------------------------------------------------------------*/
27
27
  import React from 'react';
28
- import { useTheme, getFocusableElements } from '../utils';
28
+ import { useTheme, getFocusableElements, useVirtualization, mergeRefs, } from '../utils';
29
29
  import '@itwin/itwinui-css/css/tree.css';
30
30
  import cx from 'classnames';
31
31
  import { TreeContext } from './TreeContext';
@@ -80,7 +80,7 @@ import { TreeContext } from './TreeContext';
80
80
  />
81
81
  */
82
82
  export var Tree = function (props) {
83
- var data = props.data, className = props.className, nodeRenderer = props.nodeRenderer, getNode = props.getNode, rest = __rest(props, ["data", "className", "nodeRenderer", "getNode"]);
83
+ 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"]);
84
84
  useTheme();
85
85
  var treeRef = React.useRef(null);
86
86
  var focusedIndex = React.useRef(0);
@@ -118,7 +118,7 @@ export var Tree = function (props) {
118
118
  break;
119
119
  }
120
120
  };
121
- var _a = React.useMemo(function () {
121
+ var _b = React.useMemo(function () {
122
122
  var flatList = [];
123
123
  var firstLevelNodes = [];
124
124
  var flattenNodes = function (nodes, depth, parentNode) {
@@ -147,24 +147,72 @@ export var Tree = function (props) {
147
147
  };
148
148
  flattenNodes(data);
149
149
  return [flatList, firstLevelNodes];
150
- }, [data, getNode]), flatNodesList = _a[0], firstLevelNodesList = _a[1];
151
- return (React.createElement("ul", __assign({ className: cx('iui-tree', className), role: 'tree', onKeyDown: handleKeyDown, ref: treeRef, tabIndex: 0, onFocus: function () {
152
- var _a;
153
- var items = getFocusableNodes();
154
- if (items.length > 0) {
155
- (_a = items[focusedIndex.current]) === null || _a === void 0 ? void 0 : _a.focus();
156
- }
157
- } }, rest), flatNodesList.map(function (flatNode) {
150
+ }, [data, getNode]), flatNodesList = _b[0], firstLevelNodesList = _b[1];
151
+ var itemRenderer = React.useCallback(function (index) {
158
152
  var _a, _b, _c, _d;
159
- return (React.createElement(TreeContext.Provider, { key: flatNode.nodeProps.nodeId, value: {
160
- nodeDepth: flatNode.depth,
161
- subNodeIds: flatNode.subNodeIds,
162
- groupSize: flatNode.depth === 0
153
+ var node = flatNodesList[index];
154
+ return (React.createElement(TreeContext.Provider, { key: node.nodeProps.nodeId, value: {
155
+ nodeDepth: node.depth,
156
+ subNodeIds: node.subNodeIds,
157
+ groupSize: node.depth === 0
163
158
  ? firstLevelNodesList.length
164
- : (_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,
165
- indexInGroup: flatNode.indexInGroup,
166
- parentNodeId: (_d = flatNode.parentNode) === null || _d === void 0 ? void 0 : _d.nodeProps.nodeId,
167
- } }, nodeRenderer(flatNode.nodeProps)));
168
- })));
159
+ : (_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,
160
+ indexInGroup: node.indexInGroup,
161
+ parentNodeId: (_d = node.parentNode) === null || _d === void 0 ? void 0 : _d.nodeProps.nodeId,
162
+ scrollToParent: node.parentNode
163
+ ? function () {
164
+ var _a;
165
+ var parentNodeId = (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeProps.nodeId;
166
+ var parentNodeIndex = flatNodesList.findIndex(function (n) { return n.nodeProps.nodeId === parentNodeId; });
167
+ setScrollToIndex(parentNodeIndex);
168
+ }
169
+ : undefined,
170
+ } }, nodeRenderer(node.nodeProps)));
171
+ }, [firstLevelNodesList.length, flatNodesList, nodeRenderer]);
172
+ var _c = React.useState(), scrollToIndex = _c[0], setScrollToIndex = _c[1];
173
+ var flatNodesListRef = React.useRef(flatNodesList);
174
+ React.useEffect(function () {
175
+ flatNodesListRef.current = flatNodesList;
176
+ }, [flatNodesList]);
177
+ React.useEffect(function () {
178
+ setTimeout(function () {
179
+ var _a;
180
+ if (scrollToIndex !== undefined) {
181
+ var nodeId = flatNodesListRef.current[scrollToIndex].nodeProps.nodeId;
182
+ var nodeElement = (_a = treeRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.querySelector("#".concat(nodeId));
183
+ nodeElement === null || nodeElement === void 0 ? void 0 : nodeElement.focus();
184
+ // Need to reset that if navigating with mouse and keyboard,
185
+ // e.g. pressing arrow left to go to parent node and then with mouse
186
+ // clicking some other child node and then pressing arrow left
187
+ setScrollToIndex(undefined);
188
+ }
189
+ });
190
+ }, [scrollToIndex]);
191
+ var handleFocus = function (event) {
192
+ var _a, _b;
193
+ if ((_a = treeRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.relatedTarget)) {
194
+ return;
195
+ }
196
+ var items = getFocusableNodes();
197
+ if (items.length > 0) {
198
+ (_b = items[focusedIndex.current]) === null || _b === void 0 ? void 0 : _b.focus();
199
+ }
200
+ };
201
+ return (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualizedTree, __assign({ flatNodesList: flatNodesList, itemRenderer: itemRenderer, scrollToIndex: scrollToIndex, onFocus: handleFocus, onKeyDown: handleKeyDown, ref: treeRef, className: className, style: style }, rest))) : (React.createElement(TreeElement, __assign({ onKeyDown: handleKeyDown, onFocus: handleFocus, className: className, style: style, ref: treeRef }, rest), flatNodesList.map(function (_, i) { return itemRenderer(i); })))));
169
202
  };
203
+ var TreeElement = React.forwardRef(function (_a, ref) {
204
+ var children = _a.children, className = _a.className, rest = __rest(_a, ["children", "className"]);
205
+ return (React.createElement("ul", __assign({ className: cx('iui-tree', className), role: 'tree', ref: ref, tabIndex: 0 }, rest), children));
206
+ });
207
+ // Having virtualized tree separately prevents from running all virtualization logic
208
+ var VirtualizedTree = React.forwardRef(function (_a, ref) {
209
+ var flatNodesList = _a.flatNodesList, itemRenderer = _a.itemRenderer, scrollToIndex = _a.scrollToIndex, className = _a.className, style = _a.style, rest = __rest(_a, ["flatNodesList", "itemRenderer", "scrollToIndex", "className", "style"]);
210
+ var _b = useVirtualization({
211
+ itemsLength: flatNodesList.length,
212
+ itemRenderer: itemRenderer,
213
+ scrollToIndex: scrollToIndex,
214
+ }), outerProps = _b.outerProps, innerProps = _b.innerProps, visibleChildren = _b.visibleChildren;
215
+ return (React.createElement("div", __assign({}, __assign(__assign({}, outerProps), { className: cx(className, outerProps.className), style: __assign(__assign({}, style), outerProps.style) })),
216
+ React.createElement(TreeElement, __assign({}, innerProps, rest, { ref: mergeRefs(ref, innerProps.ref) }), visibleChildren)));
217
+ });
170
218
  export default 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;
@@ -52,7 +52,7 @@ import { useTreeContext } from './TreeContext';
52
52
  export var TreeNode = function (props) {
53
53
  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"]);
54
54
  useTheme();
55
- var _e = useTreeContext(), nodeDepth = _e.nodeDepth, _f = _e.subNodeIds, subNodeIds = _f === void 0 ? [] : _f, parentNodeId = _e.parentNodeId, groupSize = _e.groupSize, indexInGroup = _e.indexInGroup;
55
+ var _e = useTreeContext(), nodeDepth = _e.nodeDepth, _f = _e.subNodeIds, subNodeIds = _f === void 0 ? [] : _f, parentNodeId = _e.parentNodeId, scrollToParent = _e.scrollToParent, groupSize = _e.groupSize, indexInGroup = _e.indexInGroup;
56
56
  var _g = React.useState(false), isFocused = _g[0], setIsFocused = _g[1];
57
57
  var nodeRef = React.useRef(null);
58
58
  var styleDepth = React.useMemo(function () {
@@ -62,7 +62,7 @@ export var TreeNode = function (props) {
62
62
  : { marginLeft: nodeDepth ? nodeDepth * 28 : 0 };
63
63
  }, [nodeDepth]);
64
64
  var onKeyDown = function (event) {
65
- var _a, _b, _c, _d, _e, _f, _g;
65
+ var _a, _b, _c, _d, _e, _f;
66
66
  var isNodeFocused = nodeRef.current === ((_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.activeElement);
67
67
  switch (event.key) {
68
68
  case 'ArrowLeft': {
@@ -73,20 +73,19 @@ export var TreeNode = function (props) {
73
73
  break;
74
74
  }
75
75
  if (parentNodeId) {
76
- var parentNode = (_b = nodeRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.querySelector("#".concat(parentNodeId));
77
- parentNode === null || parentNode === void 0 ? void 0 : parentNode.focus();
76
+ scrollToParent === null || scrollToParent === void 0 ? void 0 : scrollToParent();
78
77
  break;
79
78
  }
80
79
  // If it is top level node (doesn't have parent node), then do nothing.
81
80
  break;
82
81
  }
83
82
  var focusableElements = getFocusableElements(nodeRef.current);
84
- var currentIndex = focusableElements.indexOf((_c = nodeRef.current) === null || _c === void 0 ? void 0 : _c.ownerDocument.activeElement);
83
+ var currentIndex = focusableElements.indexOf((_b = nodeRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.activeElement);
85
84
  if (currentIndex === 0) {
86
- (_d = nodeRef.current) === null || _d === void 0 ? void 0 : _d.focus();
85
+ (_c = nodeRef.current) === null || _c === void 0 ? void 0 : _c.focus();
87
86
  }
88
87
  else {
89
- (_e = focusableElements[currentIndex - 1]) === null || _e === void 0 ? void 0 : _e.focus();
88
+ (_d = focusableElements[currentIndex - 1]) === null || _d === void 0 ? void 0 : _d.focus();
90
89
  }
91
90
  break;
92
91
  }
@@ -98,10 +97,10 @@ export var TreeNode = function (props) {
98
97
  onExpanded(nodeId, true);
99
98
  break;
100
99
  }
101
- (_f = focusableElements[0]) === null || _f === void 0 ? void 0 : _f.focus();
100
+ (_e = focusableElements[0]) === null || _e === void 0 ? void 0 : _e.focus();
102
101
  break;
103
102
  }
104
- var currentIndex = focusableElements.indexOf((_g = nodeRef.current) === null || _g === void 0 ? void 0 : _g.ownerDocument.activeElement);
103
+ var currentIndex = focusableElements.indexOf((_f = nodeRef.current) === null || _f === void 0 ? void 0 : _f.ownerDocument.activeElement);
105
104
  if (currentIndex < focusableElements.length - 1) {
106
105
  focusableElements[currentIndex + 1].focus();
107
106
  break;
@@ -37,6 +37,6 @@ import '@itwin/itwinui-css/css/text.css';
37
37
  export var Small = React.forwardRef(function (props, ref) {
38
38
  var className = props.className, _a = props.isMuted, isMuted = _a === void 0 ? false : _a, rest = __rest(props, ["className", "isMuted"]);
39
39
  useTheme();
40
- return (React.createElement("p", __assign({ ref: ref, className: cx('iui-text-small', { 'iui-text-muted': isMuted }, className) }, rest)));
40
+ return (React.createElement("small", __assign({ ref: ref, className: cx('iui-text-small', { 'iui-text-muted': isMuted }, className) }, rest)));
41
41
  });
42
42
  export default Small;
@@ -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';
@@ -11,3 +11,4 @@ export * from './useTheme';
11
11
  export * from './useIntersection';
12
12
  export * from './useMediaQuery';
13
13
  export * from './useSafeContext';
14
+ export * from './useLatestRef';
@@ -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,19 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import React from 'react';
6
+ /**
7
+ * Hook that keeps track of the latest value in a ref.
8
+ * @private
9
+ * @example
10
+ * const { value } = props;
11
+ * const valueRef = useLatestRef(value);
12
+ */
13
+ export var useLatestRef = function (value) {
14
+ var valueRef = React.useRef(value);
15
+ React.useEffect(function () {
16
+ valueRef.current = value;
17
+ }, [value]);
18
+ return valueRef;
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "main": "cjs/index.js",