@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
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
+
16
+ ## [1.41.0](https://www.github.com/iTwin/iTwinUI-react/compare/v1.40.1...v1.41.0) (2022-07-13)
17
+
18
+ ### What's new
19
+
20
+ * **ErrorPage:** Add time-out and redirect status codes ([#715](https://www.github.com/iTwin/iTwinUI-react/issues/715)) ([a5fb85f](https://www.github.com/iTwin/iTwinUI-react/commit/a5fb85f1f0ef9517a065db02384875d7c5d87f95))
21
+ * **Modal:** Update classes through the css package update ([#724](https://www.github.com/iTwin/iTwinUI-react/issues/724)) ([d13e0bf](https://www.github.com/iTwin/iTwinUI-react/commit/d13e0bf7941581a3b8d275b744bf77ebd7ae95d4))
22
+ - Now `style` and `className` props are applied on the dialog element itself.
23
+ * Border radius was changed from `3px` to `4px` through the css package update in all elements ([#724](https://www.github.com/iTwin/iTwinUI-react/issues/724)) ([d13e0bf](https://www.github.com/iTwin/iTwinUI-react/commit/d13e0bf7941581a3b8d275b744bf77ebd7ae95d4))
24
+
25
+ ### Fixes
26
+
27
+ * **Combobox:** Allow users to reset the value ([#728](https://www.github.com/iTwin/iTwinUI-react/issues/728)) ([f6c0b0d](https://www.github.com/iTwin/iTwinUI-react/commit/f6c0b0d66ac9ac725a23e07980c9dd7298084ae4))
28
+ * **Table:** Prevent crashing when clearing editable cell ([#732](https://www.github.com/iTwin/iTwinUI-react/issues/732)) ([0dedd3b](https://www.github.com/iTwin/iTwinUI-react/commit/0dedd3b8804ce98ce836d4bbbf58db83a4f9bfde))
29
+ * **Table:** Set correct intersection root for margin to work ([#708](https://www.github.com/iTwin/iTwinUI-react/issues/708)) ([55e4848](https://www.github.com/iTwin/iTwinUI-react/commit/55e48481f95c3351c127d495bd3c20134fd4a77f))
30
+
31
+ ### [1.40.1](https://www.github.com/iTwin/iTwinUI-react/compare/v1.40.0...v1.40.1) (2022-06-17)
32
+
33
+ ### Fixes
34
+
35
+ * **Table:** Horizontal scroll is working when virtual scroll is enabled ([#711](https://www.github.com/iTwin/iTwinUI-react/issues/711)) ([d3db504](https://www.github.com/iTwin/iTwinUI-react/commit/d3db504d3a44a4da2c1f34a392f99aae478ef2a3))
36
+
3
37
  ## [1.40.0](https://www.github.com/iTwin/iTwinUI-react/compare/v1.39.0...v1.40.0) (2022-06-10)
4
38
 
5
39
  ### What's new
@@ -74,11 +74,10 @@ var ComboBox = function (props) {
74
74
  var inputRef = react_1.default.useRef(null);
75
75
  var menuRef = react_1.default.useRef(null);
76
76
  var toggleButtonRef = react_1.default.useRef(null);
77
- // Latest value of the onChange prop
78
- var onChangeProp = react_1.default.useRef(onChange);
79
- react_1.default.useEffect(function () {
80
- onChangeProp.current = onChange;
81
- }, [onChange]);
77
+ var mounted = react_1.default.useRef(false);
78
+ var valuePropRef = (0, utils_1.useLatestRef)(valueProp);
79
+ var onChangeProp = (0, utils_1.useLatestRef)(onChange);
80
+ var optionsRef = (0, utils_1.useLatestRef)(options);
82
81
  // Record to store all extra information (e.g. original indexes), where the key is the id of the option
83
82
  var optionsExtraInfoRef = react_1.default.useRef({});
84
83
  // Clear the extra info when the options change so that it can be reinitialized below
@@ -97,7 +96,9 @@ var ComboBox = function (props) {
97
96
  // Reducer where all the component-wide state is stored
98
97
  var _e = react_1.default.useReducer(helpers_1.comboBoxReducer, {
99
98
  isOpen: false,
100
- selectedIndex: -1,
99
+ selectedIndex: valueProp
100
+ ? optionsRef.current.findIndex(function (option) { return option.value === valueProp; })
101
+ : -1,
101
102
  focusedIndex: -1,
102
103
  }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
103
104
  react_1.default.useLayoutEffect(function () {
@@ -105,7 +106,7 @@ var ComboBox = function (props) {
105
106
  // When the dropdown opens
106
107
  if (isOpen) {
107
108
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); // Focus the input
108
- setFilteredOptions(options); // Reset the filtered list
109
+ setFilteredOptions(optionsRef.current); // Reset the filtered list
109
110
  dispatch(['focus']);
110
111
  }
111
112
  // When the dropdown closes
@@ -114,10 +115,10 @@ var ComboBox = function (props) {
114
115
  dispatch(['focus']);
115
116
  // Reset the input value
116
117
  setInputValue(selectedIndex != undefined && selectedIndex >= 0
117
- ? (_b = options[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
118
+ ? (_b = optionsRef.current[selectedIndex]) === null || _b === void 0 ? void 0 : _b.label
118
119
  : '');
119
120
  }
120
- }, [isOpen, options, selectedIndex]);
121
+ }, [isOpen, optionsRef, selectedIndex]);
121
122
  // Set min-width of menu to be same as input
122
123
  var _g = react_1.default.useState(0), minWidth = _g[0], setMinWidth = _g[1];
123
124
  react_1.default.useEffect(function () {
@@ -148,14 +149,14 @@ var ComboBox = function (props) {
148
149
  var value = event.currentTarget.value;
149
150
  setInputValue(value);
150
151
  dispatch(['open']); // reopen when typing
151
- 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) {
152
153
  return option.label.toLowerCase().includes(value.toLowerCase());
153
154
  }));
154
155
  if (focusedIndex != -1) {
155
156
  dispatch(['focus', -1]);
156
157
  }
157
158
  (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, event);
158
- }, [filterFunction, focusedIndex, inputProps, options]);
159
+ }, [filterFunction, focusedIndex, inputProps, optionsRef]);
159
160
  // When the value prop changes, update the selectedIndex
160
161
  react_1.default.useEffect(function () {
161
162
  dispatch([
@@ -166,14 +167,17 @@ var ComboBox = function (props) {
166
167
  // Call user-defined onChange when the value actually changes
167
168
  react_1.default.useEffect(function () {
168
169
  var _a, _b;
169
- if (selectedIndex != undefined && selectedIndex >= 0) {
170
- var value = (_a = options[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
171
- if (value === valueProp) {
172
- return;
173
- }
174
- (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
170
+ // Prevent user-defined onChange to be called on mount
171
+ if (!mounted.current) {
172
+ mounted.current = true;
173
+ return;
174
+ }
175
+ var currentValue = (_a = optionsRef.current[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
176
+ if (currentValue === valuePropRef.current || selectedIndex === -1) {
177
+ return;
175
178
  }
176
- }, [options, selectedIndex, valueProp]);
179
+ (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, currentValue);
180
+ }, [onChangeProp, optionsRef, selectedIndex, valuePropRef]);
177
181
  var getMenuItem = react_1.default.useCallback(function (option, filteredIndex) {
178
182
  var optionId = getOptionId(option, id);
179
183
  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 = {
@@ -37,6 +37,8 @@ var _500_1 = __importDefault(require("@itwin/itwinui-illustrations-react/cjs/ill
37
37
  var _502_1 = __importDefault(require("@itwin/itwinui-illustrations-react/cjs/illustrations/502"));
38
38
  var _503_1 = __importDefault(require("@itwin/itwinui-illustrations-react/cjs/illustrations/503"));
39
39
  var Error_1 = __importDefault(require("@itwin/itwinui-illustrations-react/cjs/illustrations/Error"));
40
+ var Redirect_1 = __importDefault(require("@itwin/itwinui-illustrations-react/cjs/illustrations/Redirect"));
41
+ var TimedOut_1 = __importDefault(require("@itwin/itwinui-illustrations-react/cjs/illustrations/TimedOut"));
40
42
  var react_1 = __importDefault(require("react"));
41
43
  var Button_1 = require("../Buttons/Button");
42
44
  var utils_1 = require("../utils");
@@ -50,9 +52,19 @@ require("@itwin/itwinui-css/css/non-ideal-state.css");
50
52
  var ErrorPage = function (props) {
51
53
  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"]);
52
54
  (0, utils_1.useTheme)();
53
- var defaultErrorMessages = __assign({ badGateway: 'Bad gateway', error: 'Error', forbidden: 'Forbidden', internalServerError: 'Internal server error', pageNotFound: 'Page not found', serviceUnavailable: 'Service unavailable', unauthorized: 'Unauthorized' }, translatedErrorMessages);
55
+ 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);
54
56
  function getErrorIcon() {
55
57
  switch (errorType) {
58
+ case '300':
59
+ case '301':
60
+ case '302':
61
+ case '303':
62
+ case '304':
63
+ case '305':
64
+ case '307':
65
+ case '308': {
66
+ return react_1.default.createElement(Redirect_1.default, { className: 'iui-non-ideal-state-illustration' });
67
+ }
56
68
  case '401': {
57
69
  return react_1.default.createElement(_401_1.default, { className: 'iui-non-ideal-state-illustration' });
58
70
  }
@@ -62,6 +74,10 @@ var ErrorPage = function (props) {
62
74
  case '404': {
63
75
  return react_1.default.createElement(_404_1.default, { className: 'iui-non-ideal-state-illustration' });
64
76
  }
77
+ case '408':
78
+ case '504': {
79
+ return react_1.default.createElement(TimedOut_1.default, { className: 'iui-non-ideal-state-illustration' });
80
+ }
65
81
  case '500': {
66
82
  return react_1.default.createElement(_500_1.default, { className: 'iui-non-ideal-state-illustration' });
67
83
  }
@@ -82,6 +98,16 @@ var ErrorPage = function (props) {
82
98
  return errorName;
83
99
  }
84
100
  switch (errorType) {
101
+ case '300':
102
+ case '301':
103
+ case '302':
104
+ case '303':
105
+ case '304':
106
+ case '305':
107
+ case '307':
108
+ case '308': {
109
+ return defaultErrorMessages.redirect || '';
110
+ }
85
111
  case '401': {
86
112
  return defaultErrorMessages.unauthorized;
87
113
  }
@@ -91,6 +117,10 @@ var ErrorPage = function (props) {
91
117
  case '404': {
92
118
  return defaultErrorMessages.pageNotFound;
93
119
  }
120
+ case '408':
121
+ case '504': {
122
+ return defaultErrorMessages.timedOut || '';
123
+ }
94
124
  case '500': {
95
125
  return defaultErrorMessages.internalServerError;
96
126
  }
@@ -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.
@@ -36,6 +36,7 @@ var classnames_1 = __importDefault(require("classnames"));
36
36
  var Close_1 = __importDefault(require("@itwin/itwinui-icons-react/cjs/icons/Close"));
37
37
  var utils_1 = require("../utils");
38
38
  require("@itwin/itwinui-css/css/dialog.css");
39
+ require("@itwin/itwinui-css/css/backdrop.css");
39
40
  var IconButton_1 = require("../Buttons/IconButton");
40
41
  var react_transition_group_1 = require("react-transition-group");
41
42
  /**
@@ -118,15 +119,17 @@ var Modal = function (props) {
118
119
  onClose(event);
119
120
  }
120
121
  };
121
- return !!container ? (react_dom_1.default.createPortal(react_1.default.createElement(react_transition_group_1.CSSTransition, { in: isOpen, classNames: 'iui-dialog-animation', timeout: { exit: 600 }, unmountOnExit: true },
122
+ return !!container ? (react_dom_1.default.createPortal(react_1.default.createElement(react_1.default.Fragment, null,
123
+ react_1.default.createElement("div", { className: (0, classnames_1.default)('iui-backdrop', { 'iui-backdrop-visible': isOpen }), tabIndex: -1, onKeyDown: handleKeyDown, ref: overlayRef, onMouseDown: handleMouseDown }),
122
124
  react_1.default.createElement(utils_1.FocusTrap, null,
123
- react_1.default.createElement("div", __assign({ className: (0, classnames_1.default)('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),
124
- react_1.default.createElement("div", { className: 'iui-dialog', id: id, style: style, role: 'dialog', "aria-modal": 'true' },
125
- react_1.default.createElement("div", { className: 'iui-dialog-title-bar' },
126
- react_1.default.createElement("div", { className: 'iui-dialog-title' }, title),
127
- isDismissible && (react_1.default.createElement(IconButton_1.IconButton, { size: 'small', styleType: 'borderless', onClick: onClose, "aria-label": 'Close' },
128
- react_1.default.createElement(Close_1.default, null)))),
129
- children)))), container)) : (react_1.default.createElement(react_1.default.Fragment, null));
125
+ react_1.default.createElement("div", null,
126
+ react_1.default.createElement(react_transition_group_1.CSSTransition, { in: isOpen, classNames: 'iui-dialog-animation', timeout: { exit: 600 }, unmountOnExit: true },
127
+ react_1.default.createElement("div", __assign({ className: (0, classnames_1.default)('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),
128
+ react_1.default.createElement("div", { className: 'iui-dialog-title-bar' },
129
+ react_1.default.createElement("div", { className: 'iui-dialog-title' }, title),
130
+ isDismissible && (react_1.default.createElement(IconButton_1.IconButton, { size: 'small', styleType: 'borderless', onClick: onClose, "aria-label": 'Close' },
131
+ react_1.default.createElement(Close_1.default, null)))),
132
+ children))))), container)) : (react_1.default.createElement(react_1.default.Fragment, null));
130
133
  };
131
134
  exports.Modal = Modal;
132
135
  exports.default = exports.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).
@@ -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) {
@@ -275,22 +276,27 @@ var Table = function (props) {
275
276
  });
276
277
  var headerRef = react_1.default.useRef(null);
277
278
  var bodyRef = react_1.default.useRef(null);
279
+ // Using `useState` to rerender rows when table body ref is available
280
+ var _s = react_1.default.useState(null), bodyRefState = _s[0], setBodyRefState = _s[1];
278
281
  var getPreparedRow = react_1.default.useCallback(function (index) {
279
282
  var row = page[index];
280
283
  prepareRow(row);
281
- 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 }));
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) }));
282
285
  }, [
283
286
  page,
284
- expanderCell,
285
- hasAnySubRows,
286
- instance,
287
- intersectionMargin,
288
- isRowDisabled,
289
- onRowClickHandler,
290
287
  prepareRow,
291
288
  rowProps,
289
+ intersectionMargin,
292
290
  state,
291
+ onRowClickHandler,
293
292
  subComponent,
293
+ isRowDisabled,
294
+ hasAnySubRows,
295
+ instance,
296
+ expanderCell,
297
+ bodyRefState,
298
+ enableVirtualization,
299
+ tableRowRef,
294
300
  ]);
295
301
  var virtualizedItemRenderer = react_1.default.useCallback(function (index) { return getPreparedRow(index); }, [getPreparedRow]);
296
302
  var updateStickyState = function () {
@@ -366,13 +372,13 @@ var Table = function (props) {
366
372
  'iui-zebra-striping': styleType === 'zebra-rows',
367
373
  }),
368
374
  style: { outline: 0 },
369
- }), { ref: bodyRef, onScroll: function () {
375
+ }), { ref: (0, utils_1.mergeRefs)(bodyRef, setBodyRefState), onScroll: function () {
370
376
  if (headerRef.current && bodyRef.current) {
371
377
  headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
372
378
  updateStickyState();
373
379
  }
374
380
  }, tabIndex: -1 }),
375
- 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); })))),
376
382
  isLoading && data.length === 0 && (react_1.default.createElement("div", { className: 'iui-table-empty' },
377
383
  react_1.default.createElement(ProgressIndicators_1.ProgressRadial, { indeterminate: true }))),
378
384
  isLoading && data.length !== 0 && (react_1.default.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;
@@ -30,14 +30,25 @@ 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;
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);
37
37
  isLast && ((_b = onBottomReached.current) === null || _b === void 0 ? void 0 : _b.call(onBottomReached));
38
38
  }, [isLast, onBottomReached, onRowInViewport, row.original]);
39
- var rowRef = (0, utils_1.useIntersection)(onIntersect, {
39
+ var intersectionRoot = react_1.default.useMemo(function () {
40
+ var _a, _b;
41
+ var isTableBodyScrollable = ((_a = bodyRef === null || bodyRef === void 0 ? void 0 : bodyRef.scrollHeight) !== null && _a !== void 0 ? _a : 0) > ((_b = bodyRef === null || bodyRef === void 0 ? void 0 : bodyRef.offsetHeight) !== null && _b !== void 0 ? _b : 0);
42
+ // If table body is scrollable, make it the intersection root
43
+ if (isTableBodyScrollable) {
44
+ return bodyRef;
45
+ }
46
+ // Otherwise, make the viewport the intersection root
47
+ return undefined;
48
+ }, [bodyRef]);
49
+ var intersectionRef = (0, utils_1.useIntersection)(onIntersect, {
40
50
  rootMargin: "".concat(intersectionMargin, "px"),
51
+ root: intersectionRoot,
41
52
  });
42
53
  var userRowProps = rowProps === null || rowProps === void 0 ? void 0 : rowProps(row);
43
54
  var mergedProps = __assign(__assign(__assign({}, row.getRowProps({ style: { flex: "0 0 auto", minWidth: '100%' } })), userRowProps), {
@@ -47,7 +58,7 @@ var TableRow = function (props) {
47
58
  'iui-disabled': isDisabled,
48
59
  }, userRowProps === null || userRowProps === void 0 ? void 0 : userRowProps.className),
49
60
  });
50
- var refs = (0, utils_1.useMergedRefs)(rowRef, mergedProps.ref);
61
+ var refs = (0, utils_1.useMergedRefs)(intersectionRef, mergedProps.ref, tableRowRef);
51
62
  return (react_1.default.createElement(react_1.default.Fragment, null,
52
63
  react_1.default.createElement("div", __assign({}, mergedProps, { ref: refs, onClick: function (event) {
53
64
  var _a;
@@ -97,6 +108,7 @@ exports.TableRowMemoized = react_1.default.memo(exports.TableRow, function (prev
97
108
  prevProp.rowProps === nextProp.rowProps &&
98
109
  prevProp.expanderCell === nextProp.expanderCell &&
99
110
  prevProp.tableHasSubRows === nextProp.tableHasSubRows &&
111
+ prevProp.bodyRef === nextProp.bodyRef &&
100
112
  prevProp.state.columnOrder === nextProp.state.columnOrder &&
101
113
  !nextProp.state.columnResizing.isResizingColumn &&
102
114
  prevProp.state.isTableResizing === nextProp.state.isTableResizing &&
@@ -31,6 +31,7 @@ exports.EditableCell = void 0;
31
31
  * See LICENSE.md in the project root for license terms and full copyright notice.
32
32
  *--------------------------------------------------------------------------------------------*/
33
33
  var react_1 = __importDefault(require("react"));
34
+ var utils_1 = require("../../utils");
34
35
  /**
35
36
  * Editable cell.
36
37
  * It should be passed to `cellRenderer`.
@@ -53,8 +54,9 @@ var EditableCell = function (props) {
53
54
  react_1.default.useEffect(function () {
54
55
  setValue(sanitizeString(cellProps.value));
55
56
  }, [cellProps.value]);
56
- var _b = react_1.default.useState(false), isDirty = _b[0], setIsDirty = _b[1];
57
- return (react_1.default.createElement("div", __assign({}, cellElementProps, { contentEditable: true, suppressContentEditableWarning: true }, rest, { onInput: function (e) {
57
+ var _b = react_1.default.useState((0, utils_1.getRandomValue)(10)), key = _b[0], setKey = _b[1];
58
+ var _c = react_1.default.useState(false), isDirty = _c[0], setIsDirty = _c[1];
59
+ return (react_1.default.createElement("div", __assign({}, cellElementProps, { contentEditable: true, suppressContentEditableWarning: true, key: key }, rest, { onInput: function (e) {
58
60
  var _a;
59
61
  setValue(sanitizeString(e.target.innerText));
60
62
  setIsDirty(true);
@@ -65,6 +67,9 @@ var EditableCell = function (props) {
65
67
  onCellEdit(cellProps.column.id, value, cellProps.row.original);
66
68
  }
67
69
  (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
70
+ // Prevents error when text is cleared.
71
+ // New key makes React to reattach with the DOM so it won't complain about deleted text node.
72
+ setKey((0, utils_1.getRandomValue)(10));
68
73
  }, onKeyDown: function (e) {
69
74
  var _a;
70
75
  // Prevents from adding HTML elements (div, br) inside a cell on Enter press
@@ -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`.