@itwin/itwinui-react 1.40.1 → 1.41.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.41.0](https://www.github.com/iTwin/iTwinUI-react/compare/v1.40.1...v1.41.0) (2022-07-13)
4
+
5
+ ### What's new
6
+
7
+ * **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))
8
+ * **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))
9
+ - Now `style` and `className` props are applied on the dialog element itself.
10
+ * 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))
11
+
12
+ ### Fixes
13
+
14
+ * **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))
15
+ * **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))
16
+ * **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))
17
+
3
18
  ### [1.40.1](https://www.github.com/iTwin/iTwinUI-react/compare/v1.40.0...v1.40.1) (2022-06-17)
4
19
 
5
20
  ### Fixes
@@ -74,6 +74,7 @@ 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
+ var mounted = react_1.default.useRef(false);
77
78
  // Latest value of the onChange prop
78
79
  var onChangeProp = react_1.default.useRef(onChange);
79
80
  react_1.default.useEffect(function () {
@@ -97,7 +98,9 @@ var ComboBox = function (props) {
97
98
  // Reducer where all the component-wide state is stored
98
99
  var _e = react_1.default.useReducer(helpers_1.comboBoxReducer, {
99
100
  isOpen: false,
100
- selectedIndex: -1,
101
+ selectedIndex: valueProp
102
+ ? options.findIndex(function (option) { return option.value === valueProp; })
103
+ : -1,
101
104
  focusedIndex: -1,
102
105
  }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
103
106
  react_1.default.useLayoutEffect(function () {
@@ -166,14 +169,14 @@ var ComboBox = function (props) {
166
169
  // Call user-defined onChange when the value actually changes
167
170
  react_1.default.useEffect(function () {
168
171
  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);
172
+ // Prevent user-defined onChange to be called on mount
173
+ if (!mounted.current) {
174
+ mounted.current = true;
175
+ return;
175
176
  }
176
- }, [options, selectedIndex, valueProp]);
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]);
177
180
  var getMenuItem = react_1.default.useCallback(function (option, filteredIndex) {
178
181
  var optionId = getOptionId(option, id);
179
182
  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;
@@ -275,22 +275,25 @@ var Table = function (props) {
275
275
  });
276
276
  var headerRef = react_1.default.useRef(null);
277
277
  var bodyRef = react_1.default.useRef(null);
278
+ // 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];
278
280
  var getPreparedRow = react_1.default.useCallback(function (index) {
279
281
  var row = page[index];
280
282
  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 }));
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 }));
282
284
  }, [
283
285
  page,
284
- expanderCell,
285
- hasAnySubRows,
286
- instance,
287
- intersectionMargin,
288
- isRowDisabled,
289
- onRowClickHandler,
290
286
  prepareRow,
291
287
  rowProps,
288
+ intersectionMargin,
292
289
  state,
290
+ onRowClickHandler,
293
291
  subComponent,
292
+ isRowDisabled,
293
+ hasAnySubRows,
294
+ instance,
295
+ expanderCell,
296
+ bodyRefState,
294
297
  ]);
295
298
  var virtualizedItemRenderer = react_1.default.useCallback(function (index) { return getPreparedRow(index); }, [getPreparedRow]);
296
299
  var updateStickyState = function () {
@@ -366,7 +369,7 @@ var Table = function (props) {
366
369
  'iui-zebra-striping': styleType === 'zebra-rows',
367
370
  }),
368
371
  style: { outline: 0 },
369
- }), { ref: bodyRef, onScroll: function () {
372
+ }), { ref: (0, utils_1.mergeRefs)(bodyRef, setBodyRefState), onScroll: function () {
370
373
  if (headerRef.current && bodyRef.current) {
371
374
  headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
372
375
  updateStickyState();
@@ -20,6 +20,7 @@ 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;
23
24
  }) => JSX.Element;
24
25
  export declare const TableRowMemoized: <T extends Record<string, unknown>>(props: {
25
26
  row: Row<T>;
@@ -35,4 +36,5 @@ export declare const TableRowMemoized: <T extends Record<string, unknown>>(props
35
36
  tableHasSubRows: boolean;
36
37
  tableInstance: TableInstance<T>;
37
38
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
39
+ bodyRef: HTMLDivElement | null;
38
40
  }) => 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;
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);
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
@@ -68,6 +68,7 @@ export var ComboBox = function (props) {
68
68
  var inputRef = React.useRef(null);
69
69
  var menuRef = React.useRef(null);
70
70
  var toggleButtonRef = React.useRef(null);
71
+ var mounted = React.useRef(false);
71
72
  // Latest value of the onChange prop
72
73
  var onChangeProp = React.useRef(onChange);
73
74
  React.useEffect(function () {
@@ -91,7 +92,9 @@ export var ComboBox = function (props) {
91
92
  // Reducer where all the component-wide state is stored
92
93
  var _e = React.useReducer(comboBoxReducer, {
93
94
  isOpen: false,
94
- selectedIndex: -1,
95
+ selectedIndex: valueProp
96
+ ? options.findIndex(function (option) { return option.value === valueProp; })
97
+ : -1,
95
98
  focusedIndex: -1,
96
99
  }), _f = _e[0], isOpen = _f.isOpen, selectedIndex = _f.selectedIndex, focusedIndex = _f.focusedIndex, dispatch = _e[1];
97
100
  React.useLayoutEffect(function () {
@@ -160,14 +163,14 @@ export var ComboBox = function (props) {
160
163
  // Call user-defined onChange when the value actually changes
161
164
  React.useEffect(function () {
162
165
  var _a, _b;
163
- if (selectedIndex != undefined && selectedIndex >= 0) {
164
- var value = (_a = options[selectedIndex]) === null || _a === void 0 ? void 0 : _a.value;
165
- if (value === valueProp) {
166
- return;
167
- }
168
- (_b = onChangeProp.current) === null || _b === void 0 ? void 0 : _b.call(onChangeProp, value);
166
+ // Prevent user-defined onChange to be called on mount
167
+ if (!mounted.current) {
168
+ mounted.current = true;
169
+ return;
169
170
  }
170
- }, [options, selectedIndex, valueProp]);
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]);
171
174
  var getMenuItem = React.useCallback(function (option, filteredIndex) {
172
175
  var optionId = getOptionId(option, id);
173
176
  var __originalIndex = optionsExtraInfoRef.current[optionId].__originalIndex;
@@ -1,14 +1,16 @@
1
1
  import React from 'react';
2
2
  import { CommonProps } from '../utils';
3
3
  import '@itwin/itwinui-css/css/non-ideal-state.css';
4
- export declare type ErrorPageType = '401' | '403' | '404' | '500' | '502' | '503' | 'generic';
4
+ export declare type ErrorPageType = '300' | '301' | '302' | '303' | '304' | '305' | '307' | '308' | '401' | '403' | '404' | '408' | '500' | '502' | '503' | '504' | 'generic';
5
5
  export declare type ErrorTypeTranslations = {
6
6
  badGateway: string;
7
7
  error: string;
8
8
  forbidden: string;
9
9
  internalServerError: string;
10
+ redirect?: string;
10
11
  pageNotFound: string;
11
12
  serviceUnavailable: string;
13
+ timedOut?: string;
12
14
  unauthorized: string;
13
15
  };
14
16
  export declare type ErrorPageProps = {
@@ -31,6 +31,8 @@ import Svg500 from '@itwin/itwinui-illustrations-react/cjs/illustrations/500';
31
31
  import Svg502 from '@itwin/itwinui-illustrations-react/cjs/illustrations/502';
32
32
  import Svg503 from '@itwin/itwinui-illustrations-react/cjs/illustrations/503';
33
33
  import SvgError from '@itwin/itwinui-illustrations-react/cjs/illustrations/Error';
34
+ import SvgRedirect from '@itwin/itwinui-illustrations-react/cjs/illustrations/Redirect';
35
+ import SvgTimedOut from '@itwin/itwinui-illustrations-react/cjs/illustrations/TimedOut';
34
36
  import React from 'react';
35
37
  import { Button } from '../Buttons/Button';
36
38
  import { useTheme } from '../utils';
@@ -44,9 +46,19 @@ import '@itwin/itwinui-css/css/non-ideal-state.css';
44
46
  export var ErrorPage = function (props) {
45
47
  var errorType = props.errorType, errorName = props.errorName, errorMessage = props.errorMessage, primaryButtonHandle = props.primaryButtonHandle, primaryButtonLabel = props.primaryButtonLabel, secondaryButtonHandle = props.secondaryButtonHandle, secondaryButtonLabel = props.secondaryButtonLabel, translatedErrorMessages = props.translatedErrorMessages, className = props.className, rest = __rest(props, ["errorType", "errorName", "errorMessage", "primaryButtonHandle", "primaryButtonLabel", "secondaryButtonHandle", "secondaryButtonLabel", "translatedErrorMessages", "className"]);
46
48
  useTheme();
47
- var defaultErrorMessages = __assign({ badGateway: 'Bad gateway', error: 'Error', forbidden: 'Forbidden', internalServerError: 'Internal server error', pageNotFound: 'Page not found', serviceUnavailable: 'Service unavailable', unauthorized: 'Unauthorized' }, translatedErrorMessages);
49
+ var defaultErrorMessages = __assign({ badGateway: 'Bad gateway', error: 'Error', forbidden: 'Forbidden', internalServerError: 'Internal server error', redirect: 'Redirect', pageNotFound: 'Page not found', serviceUnavailable: 'Service unavailable', timedOut: 'Timed out', unauthorized: 'Unauthorized' }, translatedErrorMessages);
48
50
  function getErrorIcon() {
49
51
  switch (errorType) {
52
+ case '300':
53
+ case '301':
54
+ case '302':
55
+ case '303':
56
+ case '304':
57
+ case '305':
58
+ case '307':
59
+ case '308': {
60
+ return React.createElement(SvgRedirect, { className: 'iui-non-ideal-state-illustration' });
61
+ }
50
62
  case '401': {
51
63
  return React.createElement(Svg401, { className: 'iui-non-ideal-state-illustration' });
52
64
  }
@@ -56,6 +68,10 @@ export var ErrorPage = function (props) {
56
68
  case '404': {
57
69
  return React.createElement(Svg404, { className: 'iui-non-ideal-state-illustration' });
58
70
  }
71
+ case '408':
72
+ case '504': {
73
+ return React.createElement(SvgTimedOut, { className: 'iui-non-ideal-state-illustration' });
74
+ }
59
75
  case '500': {
60
76
  return React.createElement(Svg500, { className: 'iui-non-ideal-state-illustration' });
61
77
  }
@@ -76,6 +92,16 @@ export var ErrorPage = function (props) {
76
92
  return errorName;
77
93
  }
78
94
  switch (errorType) {
95
+ case '300':
96
+ case '301':
97
+ case '302':
98
+ case '303':
99
+ case '304':
100
+ case '305':
101
+ case '307':
102
+ case '308': {
103
+ return defaultErrorMessages.redirect || '';
104
+ }
79
105
  case '401': {
80
106
  return defaultErrorMessages.unauthorized;
81
107
  }
@@ -85,6 +111,10 @@ export var ErrorPage = function (props) {
85
111
  case '404': {
86
112
  return defaultErrorMessages.pageNotFound;
87
113
  }
114
+ case '408':
115
+ case '504': {
116
+ return defaultErrorMessages.timedOut || '';
117
+ }
88
118
  case '500': {
89
119
  return defaultErrorMessages.internalServerError;
90
120
  }
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { CommonProps } from '../utils';
3
3
  import '@itwin/itwinui-css/css/dialog.css';
4
+ import '@itwin/itwinui-css/css/backdrop.css';
4
5
  export declare type ModalProps = {
5
6
  /**
6
7
  * Flag whether modal should be shown.
@@ -30,6 +30,7 @@ import cx from 'classnames';
30
30
  import SvgClose from '@itwin/itwinui-icons-react/cjs/icons/Close';
31
31
  import { useTheme, getContainer, getDocument, FocusTrap, } from '../utils';
32
32
  import '@itwin/itwinui-css/css/dialog.css';
33
+ import '@itwin/itwinui-css/css/backdrop.css';
33
34
  import { IconButton } from '../Buttons/IconButton';
34
35
  import { CSSTransition } from 'react-transition-group';
35
36
  /**
@@ -112,14 +113,16 @@ export var Modal = function (props) {
112
113
  onClose(event);
113
114
  }
114
115
  };
115
- return !!container ? (ReactDOM.createPortal(React.createElement(CSSTransition, { in: isOpen, classNames: 'iui-dialog-animation', timeout: { exit: 600 }, unmountOnExit: true },
116
+ return !!container ? (ReactDOM.createPortal(React.createElement(React.Fragment, null,
117
+ React.createElement("div", { className: cx('iui-backdrop', { 'iui-backdrop-visible': isOpen }), tabIndex: -1, onKeyDown: handleKeyDown, ref: overlayRef, onMouseDown: handleMouseDown }),
116
118
  React.createElement(FocusTrap, null,
117
- React.createElement("div", __assign({ className: cx('iui-dialog-backdrop', { 'iui-dialog-default': styleType === 'default' }, { 'iui-dialog-full-page': styleType === 'fullPage' }, { 'iui-dialog-visible': isOpen }, className), tabIndex: -1, onKeyDown: handleKeyDown, ref: overlayRef, onMouseDown: handleMouseDown }, rest),
118
- React.createElement("div", { className: 'iui-dialog', id: id, style: style, role: 'dialog', "aria-modal": 'true' },
119
- React.createElement("div", { className: 'iui-dialog-title-bar' },
120
- React.createElement("div", { className: 'iui-dialog-title' }, title),
121
- isDismissible && (React.createElement(IconButton, { size: 'small', styleType: 'borderless', onClick: onClose, "aria-label": 'Close' },
122
- React.createElement(SvgClose, null)))),
123
- children)))), container)) : (React.createElement(React.Fragment, null));
119
+ React.createElement("div", null,
120
+ React.createElement(CSSTransition, { in: isOpen, classNames: 'iui-dialog-animation', timeout: { exit: 600 }, unmountOnExit: true },
121
+ React.createElement("div", __assign({ className: cx('iui-dialog', { 'iui-dialog-default': styleType === 'default' }, { 'iui-dialog-full-page': styleType === 'fullPage' }, { 'iui-dialog-visible': isOpen }, className), id: id, style: style, role: 'dialog', "aria-modal": 'true' }, rest),
122
+ React.createElement("div", { className: 'iui-dialog-title-bar' },
123
+ React.createElement("div", { className: 'iui-dialog-title' }, title),
124
+ isDismissible && (React.createElement(IconButton, { size: 'small', styleType: 'borderless', onClick: onClose, "aria-label": 'Close' },
125
+ React.createElement(SvgClose, null)))),
126
+ children))))), container)) : (React.createElement(React.Fragment, null));
124
127
  };
125
128
  export default Modal;
@@ -28,7 +28,7 @@ import React from 'react';
28
28
  import cx from 'classnames';
29
29
  import { actions as TableActions, useFlexLayout, useFilters, useRowSelect, useSortBy, useTable, useExpanded, usePagination, useColumnOrder, } from 'react-table';
30
30
  import { ProgressRadial } from '../ProgressIndicators';
31
- import { useTheme, useResizeObserver } from '../utils';
31
+ import { useTheme, useResizeObserver, mergeRefs } from '../utils';
32
32
  import '@itwin/itwinui-css/css/table.css';
33
33
  import SvgSortDown from '@itwin/itwinui-icons-react/cjs/icons/SortDown';
34
34
  import SvgSortUp from '@itwin/itwinui-icons-react/cjs/icons/SortUp';
@@ -269,22 +269,25 @@ export var Table = function (props) {
269
269
  });
270
270
  var headerRef = React.useRef(null);
271
271
  var bodyRef = React.useRef(null);
272
+ // Using `useState` to rerender rows when table body ref is available
273
+ var _r = React.useState(null), bodyRefState = _r[0], setBodyRefState = _r[1];
272
274
  var getPreparedRow = React.useCallback(function (index) {
273
275
  var row = page[index];
274
276
  prepareRow(row);
275
- return (React.createElement(TableRowMemoized, { row: row, rowProps: rowProps, isLast: index === page.length - 1, onRowInViewport: onRowInViewportRef, onBottomReached: onBottomReachedRef, intersectionMargin: intersectionMargin, state: state, key: row.getRowProps().key, onClick: onRowClickHandler, subComponent: subComponent, isDisabled: !!(isRowDisabled === null || isRowDisabled === void 0 ? void 0 : isRowDisabled(row.original)), tableHasSubRows: hasAnySubRows, tableInstance: instance, expanderCell: expanderCell }));
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 }));
276
278
  }, [
277
279
  page,
278
- expanderCell,
279
- hasAnySubRows,
280
- instance,
281
- intersectionMargin,
282
- isRowDisabled,
283
- onRowClickHandler,
284
280
  prepareRow,
285
281
  rowProps,
282
+ intersectionMargin,
286
283
  state,
284
+ onRowClickHandler,
287
285
  subComponent,
286
+ isRowDisabled,
287
+ hasAnySubRows,
288
+ instance,
289
+ expanderCell,
290
+ bodyRefState,
288
291
  ]);
289
292
  var virtualizedItemRenderer = React.useCallback(function (index) { return getPreparedRow(index); }, [getPreparedRow]);
290
293
  var updateStickyState = function () {
@@ -360,7 +363,7 @@ export var Table = function (props) {
360
363
  'iui-zebra-striping': styleType === 'zebra-rows',
361
364
  }),
362
365
  style: { outline: 0 },
363
- }), { ref: bodyRef, onScroll: function () {
366
+ }), { ref: mergeRefs(bodyRef, setBodyRefState), onScroll: function () {
364
367
  if (headerRef.current && bodyRef.current) {
365
368
  headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
366
369
  updateStickyState();
@@ -20,6 +20,7 @@ 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;
23
24
  }) => JSX.Element;
24
25
  export declare const TableRowMemoized: <T extends Record<string, unknown>>(props: {
25
26
  row: Row<T>;
@@ -35,4 +36,5 @@ export declare const TableRowMemoized: <T extends Record<string, unknown>>(props
35
36
  tableHasSubRows: boolean;
36
37
  tableInstance: TableInstance<T>;
37
38
  expanderCell?: ((cellProps: CellProps<T, any>) => React.ReactNode) | undefined;
39
+ bodyRef: HTMLDivElement | null;
38
40
  }) => JSX.Element;
@@ -24,14 +24,25 @@ 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;
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;
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);
31
31
  isLast && ((_b = onBottomReached.current) === null || _b === void 0 ? void 0 : _b.call(onBottomReached));
32
32
  }, [isLast, onBottomReached, onRowInViewport, row.original]);
33
- var rowRef = useIntersection(onIntersect, {
33
+ var intersectionRoot = React.useMemo(function () {
34
+ var _a, _b;
35
+ 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);
36
+ // If table body is scrollable, make it the intersection root
37
+ if (isTableBodyScrollable) {
38
+ return bodyRef;
39
+ }
40
+ // Otherwise, make the viewport the intersection root
41
+ return undefined;
42
+ }, [bodyRef]);
43
+ var intersectionRef = useIntersection(onIntersect, {
34
44
  rootMargin: "".concat(intersectionMargin, "px"),
45
+ root: intersectionRoot,
35
46
  });
36
47
  var userRowProps = rowProps === null || rowProps === void 0 ? void 0 : rowProps(row);
37
48
  var mergedProps = __assign(__assign(__assign({}, row.getRowProps({ style: { flex: "0 0 auto", minWidth: '100%' } })), userRowProps), {
@@ -41,7 +52,7 @@ export var TableRow = function (props) {
41
52
  'iui-disabled': isDisabled,
42
53
  }, userRowProps === null || userRowProps === void 0 ? void 0 : userRowProps.className),
43
54
  });
44
- var refs = useMergedRefs(rowRef, mergedProps.ref);
55
+ var refs = useMergedRefs(intersectionRef, mergedProps.ref);
45
56
  return (React.createElement(React.Fragment, null,
46
57
  React.createElement("div", __assign({}, mergedProps, { ref: refs, onClick: function (event) {
47
58
  var _a;
@@ -90,6 +101,7 @@ export var TableRowMemoized = React.memo(TableRow, function (prevProp, nextProp)
90
101
  prevProp.rowProps === nextProp.rowProps &&
91
102
  prevProp.expanderCell === nextProp.expanderCell &&
92
103
  prevProp.tableHasSubRows === nextProp.tableHasSubRows &&
104
+ prevProp.bodyRef === nextProp.bodyRef &&
93
105
  prevProp.state.columnOrder === nextProp.state.columnOrder &&
94
106
  !nextProp.state.columnResizing.isResizingColumn &&
95
107
  prevProp.state.isTableResizing === nextProp.state.isTableResizing &&
@@ -25,6 +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 { getRandomValue } from '../../utils';
28
29
  /**
29
30
  * Editable cell.
30
31
  * It should be passed to `cellRenderer`.
@@ -47,8 +48,9 @@ export var EditableCell = function (props) {
47
48
  React.useEffect(function () {
48
49
  setValue(sanitizeString(cellProps.value));
49
50
  }, [cellProps.value]);
50
- var _b = React.useState(false), isDirty = _b[0], setIsDirty = _b[1];
51
- return (React.createElement("div", __assign({}, cellElementProps, { contentEditable: true, suppressContentEditableWarning: true }, rest, { onInput: function (e) {
51
+ var _b = React.useState(getRandomValue(10)), key = _b[0], setKey = _b[1];
52
+ var _c = React.useState(false), isDirty = _c[0], setIsDirty = _c[1];
53
+ return (React.createElement("div", __assign({}, cellElementProps, { contentEditable: true, suppressContentEditableWarning: true, key: key }, rest, { onInput: function (e) {
52
54
  var _a;
53
55
  setValue(sanitizeString(e.target.innerText));
54
56
  setIsDirty(true);
@@ -59,6 +61,9 @@ export var EditableCell = function (props) {
59
61
  onCellEdit(cellProps.column.id, value, cellProps.row.original);
60
62
  }
61
63
  (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
64
+ // Prevents error when text is cleared.
65
+ // New key makes React to reattach with the DOM so it won't complain about deleted text node.
66
+ setKey(getRandomValue(10));
62
67
  }, onKeyDown: function (e) {
63
68
  var _a;
64
69
  // Prevents from adding HTML elements (div, br) inside a cell on Enter press
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "1.40.1",
3
+ "version": "1.41.0",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "main": "cjs/index.js",
@@ -35,15 +35,15 @@
35
35
  "clean:coverage": "rimraf coverage",
36
36
  "test": "jest",
37
37
  "test:watch": "jest --watch",
38
- "createComponent": "node scripts/createComponent.js",
39
38
  "format": "prettier --config .prettierrc **/*.{tsx,ts,js} --ignore-path .gitignore --write",
40
39
  "lint": "eslint \"**/*.{js,ts,tsx}\" --max-warnings=0",
41
40
  "lint:fix": "yarn lint --fix && node ../configs/copyrightLinter.js --fix \"*/**/*.{js,ts,tsx}\"",
42
41
  "copy-files": "cpy \"../../{README,LICENSE}.md\" .",
43
- "dev": "yarn build:watch"
42
+ "dev": "yarn build:watch",
43
+ "createComponent": "node ../../scripts/createComponent.js"
44
44
  },
45
45
  "dependencies": {
46
- "@itwin/itwinui-css": "^0.59.2",
46
+ "@itwin/itwinui-css": "^0.61.0",
47
47
  "@itwin/itwinui-icons-react": "^1.10.1",
48
48
  "@itwin/itwinui-illustrations-react": "^1.3.1",
49
49
  "@tippyjs/react": "^4.2.5",