@itwin/itwinui-react 3.15.5 → 3.16.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 (135) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/DEV-cjs/core/Breadcrumbs/Breadcrumbs.js +2 -2
  3. package/DEV-cjs/core/Checkbox/Checkbox.js +4 -6
  4. package/DEV-cjs/core/ComboBox/ComboBox.js +10 -6
  5. package/DEV-cjs/core/DatePicker/DatePicker.js +9 -1
  6. package/DEV-cjs/core/Dialog/Dialog.js +1 -1
  7. package/DEV-cjs/core/Panels/Panels.js +319 -0
  8. package/DEV-cjs/core/Panels/helpers.js +62 -0
  9. package/DEV-cjs/core/Radio/Radio.js +4 -6
  10. package/DEV-cjs/core/RadioTiles/RadioTileGroup.js +9 -2
  11. package/DEV-cjs/core/Select/SelectTag.js +9 -11
  12. package/DEV-cjs/core/Select/SelectTagContainer.js +2 -2
  13. package/DEV-cjs/core/Stepper/Stepper.js +1 -0
  14. package/DEV-cjs/core/Stepper/StepperStep.js +2 -1
  15. package/DEV-cjs/core/Table/Table.js +3 -4
  16. package/DEV-cjs/core/Table/TablePaginator.js +15 -3
  17. package/DEV-cjs/core/Table/actionHandlers/selectHandler.js +10 -7
  18. package/DEV-cjs/core/Table/columns/selectionColumn.js +6 -1
  19. package/DEV-cjs/core/Tree/Tree.js +1 -0
  20. package/DEV-cjs/index.js +4 -0
  21. package/DEV-cjs/styles.js +1 -1
  22. package/DEV-cjs/utils/components/MiddleTextTruncation.js +22 -4
  23. package/DEV-cjs/utils/components/OverflowContainer.js +170 -27
  24. package/DEV-cjs/utils/hooks/index.js +1 -1
  25. package/DEV-cjs/utils/hooks/useInstance.js +38 -0
  26. package/DEV-esm/core/Breadcrumbs/Breadcrumbs.js +2 -2
  27. package/DEV-esm/core/Checkbox/Checkbox.js +5 -10
  28. package/DEV-esm/core/ComboBox/ComboBox.js +10 -6
  29. package/DEV-esm/core/DatePicker/DatePicker.js +11 -1
  30. package/DEV-esm/core/Dialog/Dialog.js +1 -1
  31. package/DEV-esm/core/Panels/Panels.js +301 -0
  32. package/DEV-esm/core/Panels/helpers.js +42 -0
  33. package/DEV-esm/core/Radio/Radio.js +4 -9
  34. package/DEV-esm/core/RadioTiles/RadioTileGroup.js +8 -2
  35. package/DEV-esm/core/Select/SelectTag.js +9 -11
  36. package/DEV-esm/core/Select/SelectTagContainer.js +2 -2
  37. package/DEV-esm/core/Stepper/Stepper.js +1 -0
  38. package/DEV-esm/core/Stepper/StepperStep.js +2 -1
  39. package/DEV-esm/core/Table/Table.js +1 -5
  40. package/DEV-esm/core/Table/TablePaginator.js +16 -3
  41. package/DEV-esm/core/Table/actionHandlers/selectHandler.js +10 -7
  42. package/DEV-esm/core/Table/columns/selectionColumn.js +6 -1
  43. package/DEV-esm/core/Tree/Tree.js +1 -0
  44. package/DEV-esm/index.js +1 -0
  45. package/DEV-esm/styles.js +1 -1
  46. package/DEV-esm/utils/components/MiddleTextTruncation.js +22 -4
  47. package/DEV-esm/utils/components/OverflowContainer.js +143 -4
  48. package/DEV-esm/utils/hooks/index.js +1 -1
  49. package/DEV-esm/utils/hooks/useInstance.js +18 -0
  50. package/cjs/core/Breadcrumbs/Breadcrumbs.js +2 -2
  51. package/cjs/core/Checkbox/Checkbox.js +4 -6
  52. package/cjs/core/ComboBox/ComboBox.d.ts +13 -0
  53. package/cjs/core/ComboBox/ComboBox.js +10 -6
  54. package/cjs/core/DatePicker/DatePicker.d.ts +2 -2
  55. package/cjs/core/DatePicker/DatePicker.js +2 -1
  56. package/cjs/core/Dialog/Dialog.js +1 -1
  57. package/cjs/core/Dialog/DialogContext.d.ts +6 -2
  58. package/cjs/core/Panels/Panels.d.ts +174 -0
  59. package/cjs/core/Panels/Panels.js +312 -0
  60. package/cjs/core/Panels/helpers.d.ts +23 -0
  61. package/cjs/core/Panels/helpers.js +61 -0
  62. package/cjs/core/Radio/Radio.js +4 -6
  63. package/cjs/core/RadioTiles/RadioTileGroup.d.ts +3 -1
  64. package/cjs/core/RadioTiles/RadioTileGroup.js +9 -2
  65. package/cjs/core/Select/SelectTag.d.ts +3 -1
  66. package/cjs/core/Select/SelectTag.js +9 -11
  67. package/cjs/core/Select/SelectTagContainer.js +2 -2
  68. package/cjs/core/Stepper/Stepper.d.ts +4 -0
  69. package/cjs/core/Stepper/Stepper.js +1 -0
  70. package/cjs/core/Stepper/StepperStep.d.ts +4 -0
  71. package/cjs/core/Stepper/StepperStep.js +2 -1
  72. package/cjs/core/Table/Table.d.ts +1 -0
  73. package/cjs/core/Table/Table.js +3 -4
  74. package/cjs/core/Table/TablePaginator.js +15 -3
  75. package/cjs/core/Table/actionHandlers/selectHandler.js +10 -7
  76. package/cjs/core/Table/columns/selectionColumn.js +6 -1
  77. package/cjs/core/Tree/Tree.js +1 -0
  78. package/cjs/index.d.ts +1 -0
  79. package/cjs/index.js +4 -0
  80. package/cjs/styles.js +1 -1
  81. package/cjs/utils/components/MiddleTextTruncation.d.ts +5 -7
  82. package/cjs/utils/components/MiddleTextTruncation.js +22 -4
  83. package/cjs/utils/components/OverflowContainer.d.ts +1 -0
  84. package/cjs/utils/components/OverflowContainer.js +170 -27
  85. package/cjs/utils/hooks/index.d.ts +1 -1
  86. package/cjs/utils/hooks/index.js +1 -1
  87. package/cjs/utils/hooks/useInstance.d.ts +22 -0
  88. package/cjs/utils/hooks/useInstance.js +38 -0
  89. package/esm/core/Breadcrumbs/Breadcrumbs.js +2 -2
  90. package/esm/core/Checkbox/Checkbox.js +5 -10
  91. package/esm/core/ComboBox/ComboBox.d.ts +13 -0
  92. package/esm/core/ComboBox/ComboBox.js +10 -6
  93. package/esm/core/DatePicker/DatePicker.d.ts +2 -2
  94. package/esm/core/DatePicker/DatePicker.js +4 -1
  95. package/esm/core/Dialog/Dialog.js +1 -1
  96. package/esm/core/Dialog/DialogContext.d.ts +6 -2
  97. package/esm/core/Panels/Panels.d.ts +174 -0
  98. package/esm/core/Panels/Panels.js +294 -0
  99. package/esm/core/Panels/helpers.d.ts +23 -0
  100. package/esm/core/Panels/helpers.js +41 -0
  101. package/esm/core/Radio/Radio.js +4 -9
  102. package/esm/core/RadioTiles/RadioTileGroup.d.ts +3 -1
  103. package/esm/core/RadioTiles/RadioTileGroup.js +8 -2
  104. package/esm/core/Select/SelectTag.d.ts +3 -1
  105. package/esm/core/Select/SelectTag.js +9 -11
  106. package/esm/core/Select/SelectTagContainer.js +2 -2
  107. package/esm/core/Stepper/Stepper.d.ts +4 -0
  108. package/esm/core/Stepper/Stepper.js +1 -0
  109. package/esm/core/Stepper/StepperStep.d.ts +4 -0
  110. package/esm/core/Stepper/StepperStep.js +2 -1
  111. package/esm/core/Table/Table.d.ts +1 -0
  112. package/esm/core/Table/Table.js +1 -5
  113. package/esm/core/Table/TablePaginator.js +16 -3
  114. package/esm/core/Table/actionHandlers/selectHandler.js +10 -7
  115. package/esm/core/Table/columns/selectionColumn.js +6 -1
  116. package/esm/core/Tree/Tree.js +1 -0
  117. package/esm/index.d.ts +1 -0
  118. package/esm/index.js +1 -0
  119. package/esm/styles.js +1 -1
  120. package/esm/utils/components/MiddleTextTruncation.d.ts +5 -7
  121. package/esm/utils/components/MiddleTextTruncation.js +22 -4
  122. package/esm/utils/components/OverflowContainer.d.ts +1 -0
  123. package/esm/utils/components/OverflowContainer.js +143 -4
  124. package/esm/utils/hooks/index.d.ts +1 -1
  125. package/esm/utils/hooks/index.js +1 -1
  126. package/esm/utils/hooks/useInstance.d.ts +22 -0
  127. package/esm/utils/hooks/useInstance.js +18 -0
  128. package/package.json +2 -2
  129. package/styles.css +8 -8
  130. package/DEV-cjs/utils/hooks/useOverflow.js +0 -76
  131. package/DEV-esm/utils/hooks/useOverflow.js +0 -63
  132. package/cjs/utils/hooks/useOverflow.d.ts +0 -23
  133. package/cjs/utils/hooks/useOverflow.js +0 -76
  134. package/esm/utils/hooks/useOverflow.d.ts +0 -23
  135. package/esm/utils/hooks/useOverflow.js +0 -63
@@ -53,6 +53,7 @@ export const Stepper = React.forwardRef((props, ref) => {
53
53
  type: type,
54
54
  onClick: onStepClick,
55
55
  description: s.description,
56
+ stepContent: s.stepContent,
56
57
  });
57
58
  }),
58
59
  ),
@@ -29,6 +29,10 @@ export type StepperStepProps = {
29
29
  * A tooltip giving detailed description to this step.
30
30
  */
31
31
  description?: string;
32
+ /**
33
+ * Custom content displayed in the step's circle.
34
+ */
35
+ stepContent?: () => React.ReactNode;
32
36
  /**
33
37
  * Allows props to be passed for stepper step.
34
38
  */
@@ -17,6 +17,7 @@ export const StepperStep = React.forwardRef((props, forwardedRef) => {
17
17
  trackContentProps,
18
18
  circleProps,
19
19
  nameProps,
20
+ stepContent,
20
21
  ...rest
21
22
  } = props;
22
23
  let isPast = currentStepNumber > index;
@@ -74,7 +75,7 @@ export const StepperStep = React.forwardRef((props, forwardedRef) => {
74
75
  ...circleProps,
75
76
  className: cx('iui-stepper-circle', circleProps?.className),
76
77
  },
77
- index + 1,
78
+ stepContent ? stepContent() : index + 1,
78
79
  ),
79
80
  ),
80
81
  'default' === type &&
@@ -3,6 +3,7 @@ import type { CellProps, TableOptions, Row, TableState } from '../../react-table
3
3
  import type { CommonProps } from '../../utils/index.js';
4
4
  import type { TableFilterValue } from './filters/index.js';
5
5
  export declare const tableResizeStartAction = "tableResizeStart";
6
+ export declare const iuiId: unique symbol;
6
7
  export type TablePaginatorRendererProps = {
7
8
  /**
8
9
  * The zero-based index of the current page.
@@ -53,7 +53,7 @@ let singleRowSelectedAction = 'singleRowSelected';
53
53
  let shiftRowSelectedAction = 'shiftRowSelected';
54
54
  export const tableResizeStartAction = 'tableResizeStart';
55
55
  let tableResizeEndAction = 'tableResizeEnd';
56
- let iuiId = Symbol('iui-id');
56
+ export const iuiId = Symbol('iui-id');
57
57
  let flattenColumns = (columns) => {
58
58
  let flatColumns = [];
59
59
  columns.forEach((column) => {
@@ -657,11 +657,7 @@ export const Table = (props) => {
657
657
  },
658
658
  bodyProps?.className,
659
659
  ),
660
- style: {
661
- outline: 0,
662
- },
663
660
  }),
664
- tabIndex: -1,
665
661
  'aria-multiselectable':
666
662
  (isSelectable && 'multi' === selectionMode) || void 0,
667
663
  },
@@ -13,6 +13,7 @@ import {
13
13
  SvgChevronRight,
14
14
  Box,
15
15
  OverflowContainer,
16
+ useLayoutEffect,
16
17
  } from '../../utils/index.js';
17
18
  import { styles } from '../../styles.js';
18
19
  let defaultLocalization = {
@@ -56,7 +57,7 @@ export const TablePaginator = (props) => {
56
57
  );
57
58
  let pageListRef = React.useRef(null);
58
59
  let [focusedIndex, setFocusedIndex] = React.useState(currentPage);
59
- React.useLayoutEffect(() => {
60
+ useLayoutEffect(() => {
60
61
  setFocusedIndex(currentPage);
61
62
  }, [currentPage]);
62
63
  let needFocus = React.useRef(false);
@@ -178,6 +179,7 @@ export const TablePaginator = (props) => {
178
179
  : React.createElement(TablePaginatorPageButtons, {
179
180
  size: size,
180
181
  focusedIndex: focusedIndex,
182
+ setFocusedIndex: setFocusedIndex,
181
183
  totalPagesCount: totalPagesCount,
182
184
  onPageChange: onPageChange,
183
185
  currentPage: currentPage,
@@ -251,6 +253,7 @@ export const TablePaginator = (props) => {
251
253
  let TablePaginatorPageButtons = (props) => {
252
254
  let {
253
255
  focusedIndex,
256
+ setFocusedIndex,
254
257
  totalPagesCount,
255
258
  onPageChange,
256
259
  currentPage,
@@ -270,14 +273,24 @@ let TablePaginatorPageButtons = (props) => {
270
273
  styleType: 'borderless',
271
274
  size: buttonSize,
272
275
  'data-iui-active': index === currentPage,
273
- onClick: () => onPageChange(index),
276
+ onClick: () => {
277
+ setFocusedIndex(index);
278
+ onPageChange(index);
279
+ },
274
280
  'aria-current': index === currentPage,
275
281
  'aria-label': localization.goToPageLabel?.(index + 1),
276
282
  tabIndex: tabIndex,
277
283
  },
278
284
  index + 1,
279
285
  ),
280
- [focusedIndex, currentPage, localization, buttonSize, onPageChange],
286
+ [
287
+ focusedIndex,
288
+ buttonSize,
289
+ currentPage,
290
+ localization,
291
+ setFocusedIndex,
292
+ onPageChange,
293
+ ],
281
294
  );
282
295
  let pageList = React.useMemo(
283
296
  () =>
@@ -1,3 +1,4 @@
1
+ import { iuiId } from '../Table.js';
1
2
  let onSelectHandler = (newState, instance, onSelect, isRowDisabled) => {
2
3
  if (!instance?.rows.length) {
3
4
  onSelect?.([], newState);
@@ -7,14 +8,16 @@ let onSelectHandler = (newState, instance, onSelect, isRowDisabled) => {
7
8
  let handleRow = (row) => {
8
9
  if (isRowDisabled?.(row.original)) return false;
9
10
  let isAllSubSelected = true;
10
- row.initialSubRows.forEach((subRow) => {
11
- let result = handleRow(subRow);
12
- if (!result) isAllSubSelected = false;
13
- });
11
+ if (row.initialSubRows[0]?.original[iuiId] === void 0)
12
+ row.initialSubRows.forEach((subRow) => {
13
+ let result = handleRow(subRow);
14
+ if (!result) isAllSubSelected = false;
15
+ });
14
16
  if (
15
- (!instance.selectSubRows && newState.selectedRowIds[row.id]) ||
16
- (!row.initialSubRows.length && newState.selectedRowIds[row.id]) ||
17
- (row.initialSubRows.length && isAllSubSelected)
17
+ newState.selectedRowIds[row.id] &&
18
+ (!instance.selectSubRows ||
19
+ !row.initialSubRows.length ||
20
+ isAllSubSelected)
18
21
  )
19
22
  newSelectedRowIds[row.id] = true;
20
23
  return !!newSelectedRowIds[row.id];
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { Checkbox } from '../../Checkbox/Checkbox.js';
3
3
  import { DefaultCell } from '../cells/index.js';
4
+ import { iuiId } from '../Table.js';
4
5
  export const SELECTION_CELL_ID = 'iui-table-checkbox-selector';
5
6
  export const SelectionColumn = (props = {}) => {
6
7
  let { isDisabled, density } = props;
@@ -50,7 +51,11 @@ export const SelectionColumn = (props = {}) => {
50
51
  disabled: isDisabled?.(row.original),
51
52
  onClick: (e) => e.stopPropagation(),
52
53
  onChange: () => {
53
- if (row.subRows.length > 0 && selectSubRows)
54
+ if (
55
+ row.subRows.length > 0 &&
56
+ selectSubRows &&
57
+ void 0 === row.initialSubRows[0].original[iuiId]
58
+ )
54
59
  row.toggleRowSelected(
55
60
  !row.subRows.every(
56
61
  (subRow) => subRow.isSelected || isDisabled?.(subRow.original),
@@ -164,6 +164,7 @@ export const Tree = (props) => {
164
164
  onKeyDown: handleKeyDown,
165
165
  ref: treeRef,
166
166
  className: className,
167
+ 'data-iui-size': 'small' === size ? 'small' : void 0,
167
168
  style: style,
168
169
  ...rest,
169
170
  })
package/esm/index.d.ts CHANGED
@@ -62,6 +62,7 @@ export { ModalContent } from './core/Modal/ModalContent.js';
62
62
  export { ModalButtonBar } from './core/Modal/ModalButtonBar.js';
63
63
  export { NotificationMarker } from './core/NotificationMarker/NotificationMarker.js';
64
64
  export { Overlay } from './core/Overlay/Overlay.js';
65
+ export { Panels as unstable_Panels } from './core/Panels/Panels.js';
65
66
  export { ProgressLinear } from './core/ProgressIndicators/ProgressLinear.js';
66
67
  export { ProgressRadial } from './core/ProgressIndicators/ProgressRadial.js';
67
68
  export { Radio } from './core/Radio/Radio.js';
package/esm/index.js CHANGED
@@ -64,6 +64,7 @@ export { ModalContent } from './core/Modal/ModalContent.js';
64
64
  export { ModalButtonBar } from './core/Modal/ModalButtonBar.js';
65
65
  export { NotificationMarker } from './core/NotificationMarker/NotificationMarker.js';
66
66
  export { Overlay } from './core/Overlay/Overlay.js';
67
+ export { Panels as unstable_Panels } from './core/Panels/Panels.js';
67
68
  export { ProgressLinear } from './core/ProgressIndicators/ProgressLinear.js';
68
69
  export { ProgressRadial } from './core/ProgressIndicators/ProgressRadial.js';
69
70
  export { Radio } from './core/Radio/Radio.js';
package/esm/styles.js CHANGED
@@ -1,4 +1,4 @@
1
- const t = '3.15.5';
1
+ const t = '3.16.0';
2
2
  const u = new Proxy(
3
3
  {},
4
4
  {
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import type { CommonProps } from '../props.js';
3
- export type MiddleTextTruncationProps = {
2
+ import type { PolymorphicForwardRefComponent } from '../props.js';
3
+ type MiddleTextTruncationProps = {
4
4
  /**
5
5
  * Text to truncate.
6
6
  */
@@ -14,7 +14,7 @@ export type MiddleTextTruncationProps = {
14
14
  * Custom renderer for the truncated text.
15
15
  */
16
16
  textRenderer?: (truncatedText: string, originalText: string) => React.ReactNode;
17
- } & CommonProps;
17
+ };
18
18
  /**
19
19
  * Truncates text with the ellipsis in the middle,
20
20
  * leaving defined number of chars at the end.
@@ -31,7 +31,5 @@ export type MiddleTextTruncationProps = {
31
31
  * )}
32
32
  * />
33
33
  */
34
- export declare const MiddleTextTruncation: {
35
- (props: MiddleTextTruncationProps): React.JSX.Element;
36
- displayName: string;
37
- };
34
+ export declare const MiddleTextTruncation: PolymorphicForwardRefComponent<"span", MiddleTextTruncationProps>;
35
+ export {};
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import { OverflowContainer } from './OverflowContainer.js';
3
+ import { VisuallyHidden } from '../../core/VisuallyHidden/VisuallyHidden.js';
4
+ import { ShadowRoot } from './ShadowRoot.js';
3
5
  let ELLIPSIS_CHAR = '…';
4
- export const MiddleTextTruncation = (props) => {
5
- let { text, style, ...rest } = props;
6
+ export const MiddleTextTruncation = React.forwardRef((props, forwardedRef) => {
7
+ let { text, endCharsCount, textRenderer, style, ...rest } = props;
6
8
  return React.createElement(
7
9
  OverflowContainer,
8
10
  {
@@ -16,10 +18,26 @@ export const MiddleTextTruncation = (props) => {
16
18
  },
17
19
  itemsCount: text.length,
18
20
  ...rest,
21
+ ref: forwardedRef,
19
22
  },
20
- React.createElement(MiddleTextTruncationContent, props),
23
+ React.createElement(
24
+ ShadowRoot,
25
+ null,
26
+ React.createElement(VisuallyHidden, null, text),
27
+ React.createElement('slot', {
28
+ 'aria-hidden': true,
29
+ style: {
30
+ pointerEvents: 'none',
31
+ },
32
+ }),
33
+ ),
34
+ React.createElement(MiddleTextTruncationContent, {
35
+ text: text,
36
+ endCharsCount: endCharsCount,
37
+ textRenderer: textRenderer,
38
+ }),
21
39
  );
22
- };
40
+ });
23
41
  let MiddleTextTruncationContent = (props) => {
24
42
  let { text, endCharsCount = 6, textRenderer } = props;
25
43
  let { visibleCount } = OverflowContainer.useContext();
@@ -15,6 +15,7 @@ type OverflowContainerOverflowNodeProps = {
15
15
  children: React.ReactNode;
16
16
  };
17
17
  /**
18
+ * @private
18
19
  * Wrapper over `useOverflow`.
19
20
  *
20
21
  * - Use `OverflowContainer.useContext()` to get overflow related properties.
@@ -1,13 +1,14 @@
1
1
  import React from 'react';
2
2
  import { useMergedRefs } from '../hooks/useMergedRefs.js';
3
3
  import { Box } from './Box.js';
4
- import { useOverflow } from '../hooks/useOverflow.js';
4
+ import { useLayoutEffect } from '../hooks/useIsomorphicLayoutEffect.js';
5
5
  import { useSafeContext } from '../hooks/useSafeContext.js';
6
- let OverflowContainerComponent = React.forwardRef((props, ref) => {
6
+ import { isUnitTest } from '../functions/dev.js';
7
+ import { useResizeObserver } from '../hooks/useResizeObserver.js';
8
+ let OverflowContainerMain = React.forwardRef((props, forwardedRef) => {
7
9
  let { itemsCount, children, overflowOrientation, ...rest } = props;
8
10
  let [containerRef, visibleCount] = useOverflow(
9
11
  itemsCount,
10
- false,
11
12
  overflowOrientation,
12
13
  );
13
14
  let overflowContainerContextValue = React.useMemo(
@@ -25,7 +26,7 @@ let OverflowContainerComponent = React.forwardRef((props, ref) => {
25
26
  React.createElement(
26
27
  Box,
27
28
  {
28
- ref: useMergedRefs(ref, containerRef),
29
+ ref: useMergedRefs(forwardedRef, containerRef),
29
30
  ...rest,
30
31
  },
31
32
  children,
@@ -38,11 +39,149 @@ let OverflowContainerOverflowNode = (props) => {
38
39
  let isOverflowing = visibleCount < itemsCount;
39
40
  return isOverflowing ? children : null;
40
41
  };
42
+ let OverflowContainerComponent = React.forwardRef((props, forwardedRef) => {
43
+ let { itemsCount, overflowOrientation = 'horizontal', ...rest } = props;
44
+ let [size, setSize] = React.useState(null);
45
+ let [resizeRef] = useResizeObserver(setSize);
46
+ let ref = useMergedRefs(resizeRef, forwardedRef);
47
+ let key = `${itemsCount}${
48
+ 'vertical' === overflowOrientation ? size?.height : size?.width
49
+ }`;
50
+ return React.createElement(OverflowContainerMain, {
51
+ ...rest,
52
+ key: key,
53
+ ref: ref,
54
+ itemsCount: itemsCount,
55
+ overflowOrientation: overflowOrientation,
56
+ });
57
+ });
41
58
  export const OverflowContainer = Object.assign(OverflowContainerComponent, {
42
59
  OverflowNode: OverflowContainerOverflowNode,
43
60
  useContext: useOverflowContainerContext,
44
61
  });
45
62
  let OverflowContainerContext = React.createContext(void 0);
63
+ let useOverflow = (itemsCount, orientation = 'horizontal') => {
64
+ let [guessState, dispatch] = React.useReducer(
65
+ overflowGuessReducer,
66
+ {
67
+ itemsCount,
68
+ },
69
+ overflowGuessReducerInitialState,
70
+ );
71
+ let containerRef = React.useRef(null);
72
+ let isGuessing = React.useRef(false);
73
+ useLayoutEffect(() => {
74
+ let { minGuess, maxGuess, isStabilized, visibleCount } = guessState;
75
+ if (isStabilized) return;
76
+ guessVisibleCount();
77
+ function guessVisibleCount() {
78
+ if (isStabilized || isGuessing.current || isUnitTest) return;
79
+ try {
80
+ isGuessing.current = true;
81
+ if (null == containerRef.current) return;
82
+ let dimension = 'horizontal' === orientation ? 'Width' : 'Height';
83
+ let availableSize = containerRef.current[`offset${dimension}`];
84
+ let requiredSize = containerRef.current[`scroll${dimension}`];
85
+ let isOverflowing = availableSize < requiredSize;
86
+ if (
87
+ 0 === itemsCount ||
88
+ (1 === visibleCount && isOverflowing) ||
89
+ (visibleCount === itemsCount && !isOverflowing) ||
90
+ (maxGuess - minGuess === 1 && visibleCount === minGuess)
91
+ ) {
92
+ dispatch({
93
+ type: 'stabilize',
94
+ });
95
+ return;
96
+ }
97
+ if (maxGuess === visibleCount && !isOverflowing) {
98
+ dispatch({
99
+ type: 'shiftGuessRangeForward',
100
+ });
101
+ return;
102
+ }
103
+ isOverflowing
104
+ ? dispatch({
105
+ type: 'decreaseMaxGuess',
106
+ currentState: guessState,
107
+ })
108
+ : dispatch({
109
+ type: 'increaseMinGuess',
110
+ currentState: guessState,
111
+ });
112
+ } finally {
113
+ isGuessing.current = false;
114
+ }
115
+ }
116
+ }, [guessState, itemsCount, orientation]);
117
+ return [containerRef, guessState.visibleCount];
118
+ };
119
+ let STARTING_MAX_ITEMS_COUNT = 32;
120
+ let overflowGuessReducerInitialState = ({ itemsCount }) => {
121
+ let initialVisibleCount = Math.min(itemsCount, STARTING_MAX_ITEMS_COUNT);
122
+ return isUnitTest
123
+ ? {
124
+ isStabilized: true,
125
+ minGuess: null,
126
+ maxGuess: null,
127
+ itemsCount,
128
+ visibleCount: itemsCount,
129
+ }
130
+ : {
131
+ isStabilized: false,
132
+ minGuess: 0,
133
+ maxGuess: initialVisibleCount,
134
+ itemsCount,
135
+ visibleCount: initialVisibleCount,
136
+ };
137
+ };
138
+ let overflowGuessReducer = (state, action) => {
139
+ let getSafeVisibleCount = ({ visibleCount, itemsCount }) =>
140
+ Math.min(itemsCount, visibleCount);
141
+ switch (action.type) {
142
+ case 'decreaseMaxGuess':
143
+ case 'increaseMinGuess':
144
+ if (state.isStabilized) return state;
145
+ let newMinGuess = state.minGuess;
146
+ let newMaxGuess = state.maxGuess;
147
+ if ('decreaseMaxGuess' === action.type)
148
+ newMaxGuess = action.currentState.visibleCount;
149
+ else newMinGuess = action.currentState.visibleCount;
150
+ let newVisibleCount = Math.floor((newMinGuess + newMaxGuess) / 2);
151
+ return {
152
+ ...state,
153
+ isStabilized: false,
154
+ minGuess: newMinGuess,
155
+ maxGuess: newMaxGuess,
156
+ visibleCount: getSafeVisibleCount({
157
+ visibleCount: newVisibleCount,
158
+ itemsCount: state.itemsCount,
159
+ }),
160
+ };
161
+ case 'shiftGuessRangeForward':
162
+ if (state.isStabilized) return state;
163
+ let doubleOfMaxGuess = 2 * state.maxGuess;
164
+ return {
165
+ ...state,
166
+ isStabilized: false,
167
+ minGuess: state.maxGuess,
168
+ maxGuess: doubleOfMaxGuess,
169
+ visibleCount: getSafeVisibleCount({
170
+ visibleCount: doubleOfMaxGuess,
171
+ itemsCount: state.itemsCount,
172
+ }),
173
+ };
174
+ case 'stabilize':
175
+ return {
176
+ ...state,
177
+ isStabilized: true,
178
+ minGuess: null,
179
+ maxGuess: null,
180
+ };
181
+ default:
182
+ return state;
183
+ }
184
+ };
46
185
  function useOverflowContainerContext() {
47
186
  let overflowContainerContext = useSafeContext(OverflowContainerContext);
48
187
  return overflowContainerContext;
@@ -1,6 +1,5 @@
1
1
  export * from './useEventListener.js';
2
2
  export * from './useMergedRefs.js';
3
- export * from './useOverflow.js';
4
3
  export * from './useResizeObserver.js';
5
4
  export * from './useContainerWidth.js';
6
5
  export * from './useGlobals.js';
@@ -14,4 +13,5 @@ export * from './useId.js';
14
13
  export * from './useControlledState.js';
15
14
  export * from './useSyncExternalStore.js';
16
15
  export * from './useVirtualScroll.js';
16
+ export * from './useInstance.js';
17
17
  export * from './useWarningLogger.js';
@@ -1,6 +1,5 @@
1
1
  export * from './useEventListener.js';
2
2
  export * from './useMergedRefs.js';
3
- export * from './useOverflow.js';
4
3
  export * from './useResizeObserver.js';
5
4
  export * from './useContainerWidth.js';
6
5
  export * from './useGlobals.js';
@@ -14,4 +13,5 @@ export * from './useId.js';
14
13
  export * from './useControlledState.js';
15
14
  export * from './useSyncExternalStore.js';
16
15
  export * from './useVirtualScroll.js';
16
+ export * from './useInstance.js';
17
17
  export * from './useWarningLogger.js';
@@ -0,0 +1,22 @@
1
+ declare class Instance {
2
+ }
3
+ export declare const useInstance: () => Instance;
4
+ /**
5
+ * Synchronizes the instance with the provided properties.
6
+ *
7
+ * @param instance Instance created by `useInstance`.
8
+ * @param properties Memoized object containing properties to be synchronized.
9
+ *
10
+ * @example
11
+ * const instance = useInstance();
12
+ *
13
+ * const properties = React.useMemo(() => ({
14
+ * show: () => console.log('show'),
15
+ * }), []);
16
+ *
17
+ * useSynchronizeInstance(instance, properties);
18
+ *
19
+ * instance.show(); // logs 'show'
20
+ */
21
+ export declare const useSynchronizeInstance: <T>(instance: T, properties: T) => T;
22
+ export {};
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import { useSyncExternalStore } from './useSyncExternalStore.js';
3
+ class Instance {}
4
+ export const useInstance = () => React.useMemo(() => new Instance(), []);
5
+ export const useSynchronizeInstance = (instance, properties) => {
6
+ let synchronize = React.useCallback(() => {
7
+ if (!(instance instanceof Instance)) return () => {};
8
+ Object.assign(instance, properties);
9
+ return () => {
10
+ for (let key in properties) delete instance[key];
11
+ };
12
+ }, [instance, properties]);
13
+ return useSyncExternalStore(
14
+ synchronize,
15
+ () => instance,
16
+ () => instance,
17
+ );
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "3.15.5",
3
+ "version": "3.16.0",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -89,7 +89,7 @@
89
89
  "react-transition-group": "^4.4.5"
90
90
  },
91
91
  "devDependencies": {
92
- "@swc/cli": "^0.3.12",
92
+ "@swc/cli": "^0.5.1",
93
93
  "@swc/core": "^1.5.28",
94
94
  "@testing-library/jest-dom": "^6.3.0",
95
95
  "@testing-library/react": "^13.2.0",