@itwin/itwinui-react 1.39.0 → 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.
Files changed (70) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/cjs/core/ComboBox/ComboBox.d.ts +10 -1
  3. package/cjs/core/ComboBox/ComboBox.js +42 -22
  4. package/cjs/core/ComboBox/ComboBoxInput.js +1 -0
  5. package/cjs/core/ComboBox/helpers.js +1 -1
  6. package/cjs/core/ErrorPage/ErrorPage.d.ts +3 -1
  7. package/cjs/core/ErrorPage/ErrorPage.js +31 -1
  8. package/cjs/core/Menu/MenuItemSkeleton.d.ts +32 -0
  9. package/cjs/core/Menu/MenuItemSkeleton.js +53 -0
  10. package/cjs/core/Menu/index.d.ts +2 -0
  11. package/cjs/core/Menu/index.js +3 -1
  12. package/cjs/core/Modal/Modal.d.ts +1 -0
  13. package/cjs/core/Modal/Modal.js +11 -8
  14. package/cjs/core/Table/Table.js +50 -14
  15. package/cjs/core/Table/TableCell.js +10 -3
  16. package/cjs/core/Table/TableRowMemoized.d.ts +2 -0
  17. package/cjs/core/Table/TableRowMemoized.js +20 -4
  18. package/cjs/core/Table/actionHandlers/resizeHandler.d.ts +8 -0
  19. package/cjs/core/Table/actionHandlers/selectHandler.d.ts +4 -0
  20. package/cjs/core/Table/cells/EditableCell.js +7 -2
  21. package/cjs/core/Table/columns/selectionColumn.js +4 -2
  22. package/cjs/core/Table/hooks/index.d.ts +1 -0
  23. package/cjs/core/Table/hooks/index.js +3 -1
  24. package/cjs/core/Table/hooks/useStickyColumns.d.ts +2 -0
  25. package/cjs/core/Table/hooks/useStickyColumns.js +84 -0
  26. package/cjs/core/Table/utils.d.ts +1 -0
  27. package/cjs/core/Table/utils.js +36 -1
  28. package/cjs/core/index.d.ts +2 -2
  29. package/cjs/core/index.js +4 -3
  30. package/cjs/core/utils/components/VirtualScroll.js +2 -2
  31. package/cjs/core/utils/components/VisuallyHidden.d.ts +9 -0
  32. package/cjs/core/utils/components/VisuallyHidden.js +44 -0
  33. package/cjs/core/utils/components/index.d.ts +1 -0
  34. package/cjs/core/utils/components/index.js +1 -0
  35. package/cjs/types/react-table-config.d.ts +9 -0
  36. package/esm/core/ComboBox/ComboBox.d.ts +10 -1
  37. package/esm/core/ComboBox/ComboBox.js +42 -22
  38. package/esm/core/ComboBox/ComboBoxInput.js +1 -0
  39. package/esm/core/ComboBox/helpers.js +1 -1
  40. package/esm/core/ErrorPage/ErrorPage.d.ts +3 -1
  41. package/esm/core/ErrorPage/ErrorPage.js +31 -1
  42. package/esm/core/Menu/MenuItemSkeleton.d.ts +32 -0
  43. package/esm/core/Menu/MenuItemSkeleton.js +46 -0
  44. package/esm/core/Menu/index.d.ts +2 -0
  45. package/esm/core/Menu/index.js +1 -0
  46. package/esm/core/Modal/Modal.d.ts +1 -0
  47. package/esm/core/Modal/Modal.js +11 -8
  48. package/esm/core/Table/Table.js +53 -17
  49. package/esm/core/Table/TableCell.js +11 -4
  50. package/esm/core/Table/TableRowMemoized.d.ts +2 -0
  51. package/esm/core/Table/TableRowMemoized.js +20 -4
  52. package/esm/core/Table/actionHandlers/resizeHandler.d.ts +8 -0
  53. package/esm/core/Table/actionHandlers/selectHandler.d.ts +4 -0
  54. package/esm/core/Table/cells/EditableCell.js +7 -2
  55. package/esm/core/Table/columns/selectionColumn.js +4 -2
  56. package/esm/core/Table/hooks/index.d.ts +1 -0
  57. package/esm/core/Table/hooks/index.js +1 -0
  58. package/esm/core/Table/hooks/useStickyColumns.d.ts +2 -0
  59. package/esm/core/Table/hooks/useStickyColumns.js +80 -0
  60. package/esm/core/Table/utils.d.ts +1 -0
  61. package/esm/core/Table/utils.js +34 -0
  62. package/esm/core/index.d.ts +2 -2
  63. package/esm/core/index.js +1 -1
  64. package/esm/core/utils/components/VirtualScroll.js +2 -2
  65. package/esm/core/utils/components/VisuallyHidden.d.ts +9 -0
  66. package/esm/core/utils/components/VisuallyHidden.js +38 -0
  67. package/esm/core/utils/components/index.d.ts +1 -0
  68. package/esm/core/utils/components/index.js +1 -0
  69. package/esm/types/react-table-config.d.ts +9 -0
  70. package/package.json +4 -4
@@ -112,6 +112,7 @@ export var ComboBoxInput = React.forwardRef(function (props, forwardedRef) {
112
112
  event.preventDefault();
113
113
  if (isOpen) {
114
114
  dispatch(['select', focusedIndexRef.current]);
115
+ dispatch(['close']);
115
116
  }
116
117
  else {
117
118
  dispatch(['open']);
@@ -25,7 +25,7 @@ export var comboBoxReducer = function (state, _a) {
25
25
  return __assign(__assign({}, state), { isOpen: false });
26
26
  }
27
27
  case 'select': {
28
- return __assign(__assign({}, state), { isOpen: false, selectedIndex: value !== null && value !== void 0 ? value : state.selectedIndex, focusedIndex: value !== null && value !== void 0 ? value : state.focusedIndex });
28
+ return __assign(__assign({}, state), { selectedIndex: value !== null && value !== void 0 ? value : state.selectedIndex, focusedIndex: value !== null && value !== void 0 ? value : state.focusedIndex });
29
29
  }
30
30
  case 'focus': {
31
31
  return __assign(__assign({}, state), { focusedIndex: (_b = value !== null && value !== void 0 ? value : state.selectedIndex) !== null && _b !== void 0 ? _b : -1 });
@@ -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
  }
@@ -0,0 +1,32 @@
1
+ /// <reference types="react" />
2
+ import { CommonProps } from '../utils';
3
+ import '@itwin/itwinui-css/css/menu.css';
4
+ export declare type MenuItemSkeletonProps = {
5
+ /**
6
+ * Flag whether to show skeleton for sub-label.
7
+ */
8
+ hasSublabel?: boolean;
9
+ /**
10
+ * Flag whether to show skeleton for icon.
11
+ */
12
+ hasIcon?: boolean;
13
+ /**
14
+ * Skeleton content width.
15
+ */
16
+ contentWidth?: string;
17
+ /**
18
+ * Translated strings used for accessibility.
19
+ */
20
+ translatedStrings?: {
21
+ /**
22
+ * Label for loading state. Defaults to "Loading…".
23
+ * It is only visible for the screen readers.
24
+ */
25
+ loading: string;
26
+ };
27
+ } & CommonProps;
28
+ /**
29
+ * Menu item that uses skeletons to indicate loading state.
30
+ */
31
+ export declare const MenuItemSkeleton: (props: MenuItemSkeletonProps) => JSX.Element;
32
+ export default MenuItemSkeleton;
@@ -0,0 +1,46 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __rest = (this && this.__rest) || function (s, e) {
13
+ var t = {};
14
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
+ t[p] = s[p];
16
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
+ t[p[i]] = s[p[i]];
20
+ }
21
+ return t;
22
+ };
23
+ /*---------------------------------------------------------------------------------------------
24
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
25
+ * See LICENSE.md in the project root for license terms and full copyright notice.
26
+ *--------------------------------------------------------------------------------------------*/
27
+ import React from 'react';
28
+ import cx from 'classnames';
29
+ import { useTheme, VisuallyHidden } from '../utils';
30
+ import '@itwin/itwinui-css/css/menu.css';
31
+ /**
32
+ * Menu item that uses skeletons to indicate loading state.
33
+ */
34
+ export var MenuItemSkeleton = function (props) {
35
+ var hasSublabel = props.hasSublabel, hasIcon = props.hasIcon, contentWidth = props.contentWidth, _a = props.translatedStrings, translatedStrings = _a === void 0 ? { loading: 'Loading…' } : _a, className = props.className, style = props.style, rest = __rest(props, ["hasSublabel", "hasIcon", "contentWidth", "translatedStrings", "className", "style"]);
36
+ useTheme();
37
+ return (React.createElement("li", __assign({ className: cx('iui-menu-item', 'iui-menu-item-skeleton', { 'iui-large': hasSublabel }, className), style: __assign({
38
+ '--iui-menu-item-content-skeleton-max-width': contentWidth,
39
+ }, style) }, rest),
40
+ hasIcon && React.createElement("div", { className: 'iui-icon iui-skeleton', "aria-hidden": true }),
41
+ React.createElement("span", { className: 'iui-content' },
42
+ React.createElement("div", { className: 'iui-menu-label iui-skeleton', "aria-hidden": true }),
43
+ hasSublabel && (React.createElement("div", { className: 'iui-menu-description iui-skeleton', "aria-hidden": true })),
44
+ React.createElement(VisuallyHidden, null, translatedStrings.loading))));
45
+ };
46
+ export default MenuItemSkeleton;
@@ -6,3 +6,5 @@ export { MenuDivider } from './MenuDivider';
6
6
  export type { MenuDividerProps } from './MenuDivider';
7
7
  export { MenuExtraContent } from './MenuExtraContent';
8
8
  export type { MenuExtraContentProps } from './MenuExtraContent';
9
+ export { MenuItemSkeleton } from './MenuItemSkeleton';
10
+ export type { MenuItemSkeletonProps } from './MenuItemSkeleton';
@@ -6,3 +6,4 @@ export { Menu } from './Menu';
6
6
  export { MenuItem } from './MenuItem';
7
7
  export { MenuDivider } from './MenuDivider';
8
8
  export { MenuExtraContent } from './MenuExtraContent';
9
+ export { MenuItemSkeleton } from './MenuItemSkeleton';
@@ -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,15 +28,15 @@ 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';
35
- import { getCellStyle } from './utils';
35
+ 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, } from './hooks';
39
+ import { useExpanderCell, useSelectionCell, useSubRowFiltering, useSubRowSelection, useResizeColumns, useColumnDragAndDrop, 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';
@@ -171,8 +171,8 @@ export var Table = function (props) {
171
171
  return getSubRows ? getSubRows(item, index) : item.subRows;
172
172
  });
173
173
  }, [data, getSubRows]);
174
- var instance = useTable(__assign(__assign({ manualPagination: !paginatorRenderer, paginateExpandedRows: false }, props), { columns: columns, defaultColumn: defaultColumn, disableSortBy: !isSortable, stateReducer: tableStateReducer, filterTypes: filterTypes, selectSubRows: selectSubRows, data: data, getSubRows: getSubRows, initialState: __assign({ pageSize: pageSize }, props.initialState) }), useFlexLayout, useResizeColumns(ownerDocument), useFilters, useSubRowFiltering(hasAnySubRows), useSortBy, useExpanded, usePagination, useRowSelect, useSubRowSelection, useExpanderCell(subComponent, expanderCell, isRowDisabled), useSelectionCell(isSelectable, selectionMode, isRowDisabled), useColumnOrder, useColumnDragAndDrop(enableColumnReordering));
175
- var getTableProps = instance.getTableProps, rows = instance.rows, headerGroups = instance.headerGroups, getTableBodyProps = instance.getTableBodyProps, prepareRow = instance.prepareRow, state = instance.state, allColumns = instance.allColumns, filteredFlatRows = instance.filteredFlatRows, dispatch = instance.dispatch, page = instance.page, gotoPage = instance.gotoPage, setPageSize = instance.setPageSize, flatHeaders = instance.flatHeaders;
174
+ var instance = useTable(__assign(__assign({ manualPagination: !paginatorRenderer, paginateExpandedRows: false }, props), { columns: columns, defaultColumn: defaultColumn, disableSortBy: !isSortable, stateReducer: tableStateReducer, filterTypes: filterTypes, selectSubRows: selectSubRows, data: data, getSubRows: getSubRows, initialState: __assign({ pageSize: pageSize }, props.initialState) }), useFlexLayout, useResizeColumns(ownerDocument), useFilters, useSubRowFiltering(hasAnySubRows), useSortBy, useExpanded, usePagination, useRowSelect, useSubRowSelection, useExpanderCell(subComponent, expanderCell, isRowDisabled), useSelectionCell(isSelectable, selectionMode, isRowDisabled), useColumnOrder, useColumnDragAndDrop(enableColumnReordering), useStickyColumns);
175
+ var getTableProps = instance.getTableProps, rows = instance.rows, headerGroups = instance.headerGroups, getTableBodyProps = instance.getTableBodyProps, prepareRow = instance.prepareRow, state = instance.state, allColumns = instance.allColumns, filteredFlatRows = instance.filteredFlatRows, dispatch = instance.dispatch, page = instance.page, gotoPage = instance.gotoPage, setPageSize = instance.setPageSize, flatHeaders = instance.flatHeaders, visibleColumns = instance.visibleColumns;
176
176
  var ariaDataAttributes = Object.entries(rest).reduce(function (result, _a) {
177
177
  var key = _a[0], value = _a[1];
178
178
  if (key.startsWith('data-') || key.startsWith('aria-')) {
@@ -269,24 +269,51 @@ 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]);
293
+ var updateStickyState = function () {
294
+ if (!bodyRef.current || flatHeaders.every(function (header) { return !header.sticky; })) {
295
+ return;
296
+ }
297
+ if (bodyRef.current.scrollLeft !== 0) {
298
+ dispatch({ type: TableActions.setScrolledRight, value: true });
299
+ }
300
+ else {
301
+ dispatch({ type: TableActions.setScrolledRight, value: false });
302
+ }
303
+ // If scrolled a bit to the left looking from the right side
304
+ if (bodyRef.current.scrollLeft !==
305
+ bodyRef.current.scrollWidth - bodyRef.current.clientWidth) {
306
+ dispatch({ type: TableActions.setScrolledLeft, value: true });
307
+ }
308
+ else {
309
+ dispatch({ type: TableActions.setScrolledLeft, value: false });
310
+ }
311
+ };
312
+ React.useEffect(function () {
313
+ updateStickyState();
314
+ // Call only on init
315
+ // eslint-disable-next-line react-hooks/exhaustive-deps
316
+ }, []);
290
317
  return (React.createElement(React.Fragment, null,
291
318
  React.createElement("div", __assign({ ref: function (element) {
292
319
  setOwnerDocument(element === null || element === void 0 ? void 0 : element.ownerDocument);
@@ -295,7 +322,7 @@ export var Table = function (props) {
295
322
  }
296
323
  }, id: id }, getTableProps({
297
324
  className: cx('iui-table', (_a = {}, _a["iui-".concat(density)] = density !== 'default', _a), className),
298
- style: style,
325
+ style: __assign({ minWidth: 0 }, style),
299
326
  }), ariaDataAttributes),
300
327
  React.createElement("div", { className: 'iui-table-header-wrapper', ref: headerRef },
301
328
  React.createElement("div", { className: 'iui-table-header' }, headerGroups.slice(1).map(function (headerGroup) {
@@ -303,9 +330,13 @@ export var Table = function (props) {
303
330
  className: 'iui-row',
304
331
  });
305
332
  return (React.createElement("div", __assign({}, headerGroupProps, { key: headerGroupProps.key }), headerGroup.headers.map(function (column, index) {
306
- var columnProps = column.getHeaderProps(__assign(__assign({}, column.getSortByToggleProps()), { className: cx('iui-cell', { 'iui-actionable': column.canSort }, { 'iui-sorted': column.isSorted }, column.columnClassName), style: getCellStyle(column, !!state.isTableResizing) }));
333
+ var columnProps = column.getHeaderProps(__assign(__assign({}, column.getSortByToggleProps()), { className: cx('iui-cell', {
334
+ 'iui-actionable': column.canSort,
335
+ 'iui-sorted': column.isSorted,
336
+ 'iui-cell-sticky': !!column.sticky,
337
+ }, column.columnClassName), style: __assign(__assign(__assign({}, getCellStyle(column, !!state.isTableResizing)), getStickyStyle(column, visibleColumns)), { flexWrap: 'unset' }) }));
307
338
  return (React.createElement("div", __assign({}, columnProps, column.getDragAndDropProps(), { key: columnProps.key, title: undefined, ref: function (el) {
308
- if (el && isResizable) {
339
+ if (el) {
309
340
  columnRefs.current[column.id] = el;
310
341
  column.resizeWidth = el.getBoundingClientRect().width;
311
342
  }
@@ -320,7 +351,11 @@ export var Table = function (props) {
320
351
  index !== headerGroup.headers.length - 1 && (React.createElement("div", __assign({}, column.getResizerProps(), { className: 'iui-resizer' }),
321
352
  React.createElement("div", { className: 'iui-resizer-bar' }))),
322
353
  enableColumnReordering &&
323
- !column.disableReordering && (React.createElement("div", { className: 'iui-reorder-bar' }))));
354
+ !column.disableReordering && (React.createElement("div", { className: 'iui-reorder-bar' })),
355
+ column.sticky === 'left' &&
356
+ state.sticky.isScrolledToRight && (React.createElement("div", { className: 'iui-cell-shadow-right' })),
357
+ column.sticky === 'right' &&
358
+ state.sticky.isScrolledToLeft && (React.createElement("div", { className: 'iui-cell-shadow-left' }))));
324
359
  })));
325
360
  }))),
326
361
  React.createElement("div", __assign({}, getTableBodyProps({
@@ -328,9 +363,10 @@ export var Table = function (props) {
328
363
  'iui-zebra-striping': styleType === 'zebra-rows',
329
364
  }),
330
365
  style: { outline: 0 },
331
- }), { ref: bodyRef, onScroll: function () {
366
+ }), { ref: mergeRefs(bodyRef, setBodyRefState), onScroll: function () {
332
367
  if (headerRef.current && bodyRef.current) {
333
368
  headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
369
+ updateStickyState();
334
370
  }
335
371
  }, tabIndex: -1 }),
336
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); })))),
@@ -15,7 +15,7 @@ var __assign = (this && this.__assign) || function () {
15
15
  *--------------------------------------------------------------------------------------------*/
16
16
  import React from 'react';
17
17
  import cx from 'classnames';
18
- import { getCellStyle } from './utils';
18
+ import { getCellStyle, getStickyStyle } from './utils';
19
19
  import { SubRowExpander } from './SubRowExpander';
20
20
  import { SELECTION_CELL_ID } from './columns';
21
21
  import { DefaultCell } from './cells';
@@ -34,8 +34,10 @@ export var TableCell = function (props) {
34
34
  };
35
35
  };
36
36
  var cellElementProps = cell.getCellProps({
37
- className: cx('iui-cell', cell.column.cellClassName),
38
- style: __assign(__assign({}, getCellStyle(cell.column, !!tableInstance.state.isTableResizing)), getSubRowStyle()),
37
+ className: cx('iui-cell', cell.column.cellClassName, {
38
+ 'iui-cell-sticky': !!cell.column.sticky,
39
+ }),
40
+ style: __assign(__assign(__assign({}, getCellStyle(cell.column, !!tableInstance.state.isTableResizing)), getSubRowStyle()), getStickyStyle(cell.column, tableInstance.visibleColumns)),
39
41
  });
40
42
  var cellProps = __assign(__assign({}, tableInstance), { cell: cell, row: cell.row, value: cell.value, column: cell.column });
41
43
  var cellContent = (React.createElement(React.Fragment, null,
@@ -44,7 +46,12 @@ export var TableCell = function (props) {
44
46
  var cellRendererProps = {
45
47
  cellElementProps: cellElementProps,
46
48
  cellProps: cellProps,
47
- children: cellContent,
49
+ children: (React.createElement(React.Fragment, null,
50
+ cellContent,
51
+ cell.column.sticky === 'left' &&
52
+ tableInstance.state.sticky.isScrolledToRight && (React.createElement("div", { className: 'iui-cell-shadow-right' })),
53
+ cell.column.sticky === 'right' &&
54
+ tableInstance.state.sticky.isScrolledToLeft && (React.createElement("div", { className: 'iui-cell-shadow-left' })))),
48
55
  };
49
56
  return (React.createElement(React.Fragment, null, cell.column.cellRenderer ? (cell.column.cellRenderer(__assign(__assign({}, cellRendererProps), { isDisabled: function () { return isDisabled; } }))) : (React.createElement(DefaultCell, __assign({}, cellRendererProps, { isDisabled: function () { return isDisabled; } })))));
50
57
  };
@@ -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,7 +101,12 @@ 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
- prevProp.state.isTableResizing === nextProp.state.isTableResizing;
107
+ prevProp.state.isTableResizing === nextProp.state.isTableResizing &&
108
+ prevProp.state.sticky.isScrolledToLeft ===
109
+ nextProp.state.sticky.isScrolledToLeft &&
110
+ prevProp.state.sticky.isScrolledToRight ===
111
+ nextProp.state.sticky.isScrolledToRight;
96
112
  });
@@ -12,6 +12,10 @@ export declare const onTableResizeStart: <T extends Record<string, unknown>>(sta
12
12
  isResizingColumn?: string | undefined;
13
13
  };
14
14
  columnReorderStartIndex: number;
15
+ sticky: {
16
+ isScrolledToRight?: boolean | undefined;
17
+ isScrolledToLeft?: boolean | undefined;
18
+ };
15
19
  columnOrder: import("react-table").IdType<T>[];
16
20
  expanded: Record<import("react-table").IdType<T>, boolean>;
17
21
  filters: import("react-table").Filters<T>;
@@ -38,6 +42,10 @@ export declare const onTableResizeEnd: <T extends Record<string, unknown>>(state
38
42
  };
39
43
  hiddenColumns?: import("react-table").IdType<T>[] | undefined;
40
44
  columnReorderStartIndex: number;
45
+ sticky: {
46
+ isScrolledToRight?: boolean | undefined;
47
+ isScrolledToLeft?: boolean | undefined;
48
+ };
41
49
  columnOrder: import("react-table").IdType<T>[];
42
50
  expanded: Record<import("react-table").IdType<T>, boolean>;
43
51
  filters: import("react-table").Filters<T>;
@@ -20,6 +20,10 @@ export declare const onSingleSelectHandler: <T extends Record<string, unknown>>(
20
20
  };
21
21
  isTableResizing?: boolean | undefined;
22
22
  columnReorderStartIndex: number;
23
+ sticky: {
24
+ isScrolledToRight?: boolean | undefined;
25
+ isScrolledToLeft?: boolean | undefined;
26
+ };
23
27
  columnOrder: import("react-table").IdType<T>[];
24
28
  expanded: Record<import("react-table").IdType<T>, boolean>;
25
29
  filters: import("react-table").Filters<T>;
@@ -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
@@ -49,11 +49,13 @@ export var SelectionColumn = function (props) {
49
49
  var getToggleAllRowsSelectedProps = _a.getToggleAllRowsSelectedProps, rows = _a.rows, initialRows = _a.initialRows, state = _a.state;
50
50
  var disabled = rows.every(function (row) { return isDisabled === null || isDisabled === void 0 ? void 0 : isDisabled(row.original); });
51
51
  var checked = initialRows.every(function (row) { return state.selectedRowIds[row.id] || (isDisabled === null || isDisabled === void 0 ? void 0 : isDisabled(row.original)); });
52
- return (React.createElement(Checkbox, __assign({}, getToggleAllRowsSelectedProps(), { style: {}, checked: checked && !disabled, indeterminate: !checked && Object.keys(state.selectedRowIds).length > 0, disabled: disabled })));
52
+ return (React.createElement(Checkbox, __assign({}, getToggleAllRowsSelectedProps(), { style: {}, title: '' // Removes default title that comes from react-table
53
+ , checked: checked && !disabled, indeterminate: !checked && Object.keys(state.selectedRowIds).length > 0, disabled: disabled })));
53
54
  },
54
55
  Cell: function (_a) {
55
56
  var row = _a.row;
56
- return (React.createElement(Checkbox, __assign({}, row.getToggleRowSelectedProps(), { style: {}, disabled: isDisabled === null || isDisabled === void 0 ? void 0 : isDisabled(row.original), onClick: function (e) { return e.stopPropagation(); } })));
57
+ return (React.createElement(Checkbox, __assign({}, row.getToggleRowSelectedProps(), { style: {}, title: '' // Removes default title that comes from react-table
58
+ , disabled: isDisabled === null || isDisabled === void 0 ? void 0 : isDisabled(row.original), onClick: function (e) { return e.stopPropagation(); } })));
57
59
  },
58
60
  cellRenderer: function (props) { return (React.createElement(DefaultCell, __assign({}, props, { isDisabled: function (rowData) { return !!(isDisabled === null || isDisabled === void 0 ? void 0 : isDisabled(rowData)); } }))); },
59
61
  };
@@ -4,3 +4,4 @@ export { useSubRowFiltering } from './useSubRowFiltering';
4
4
  export { useSubRowSelection } from './useSubRowSelection';
5
5
  export { useResizeColumns } from './useResizeColumns';
6
6
  export { useColumnDragAndDrop } from './useColumnDragAndDrop';
7
+ export { useStickyColumns } from './useStickyColumns';
@@ -8,3 +8,4 @@ export { useSubRowFiltering } from './useSubRowFiltering';
8
8
  export { useSubRowSelection } from './useSubRowSelection';
9
9
  export { useResizeColumns } from './useResizeColumns';
10
10
  export { useColumnDragAndDrop } from './useColumnDragAndDrop';
11
+ export { useStickyColumns } from './useStickyColumns';
@@ -0,0 +1,2 @@
1
+ import { Hooks } from 'react-table';
2
+ export declare const useStickyColumns: <T extends Record<string, unknown>>(hooks: Hooks<T>) => void;