@itwin/itwinui-react 1.38.0 → 1.38.1
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 +6 -0
- package/cjs/core/ComboBox/ComboBox.d.ts +7 -0
- package/cjs/core/ComboBox/ComboBox.js +18 -11
- package/cjs/core/ComboBox/ComboBoxMenu.js +38 -2
- package/cjs/core/ComboBox/ComboBoxMenuItem.js +2 -3
- package/cjs/core/ComboBox/helpers.d.ts +8 -3
- package/cjs/core/Select/Select.js +1 -1
- package/cjs/core/Toast/Toaster.d.ts +1 -1
- package/cjs/core/Toast/Toaster.js +28 -64
- package/cjs/core/utils/components/VirtualScroll.d.ts +35 -1
- package/cjs/core/utils/components/VirtualScroll.js +159 -26
- package/esm/core/ComboBox/ComboBox.d.ts +7 -0
- package/esm/core/ComboBox/ComboBox.js +18 -11
- package/esm/core/ComboBox/ComboBoxMenu.js +39 -3
- package/esm/core/ComboBox/ComboBoxMenuItem.js +2 -3
- package/esm/core/ComboBox/helpers.d.ts +8 -3
- package/esm/core/Select/Select.js +1 -1
- package/esm/core/Toast/Toaster.d.ts +1 -1
- package/esm/core/Toast/Toaster.js +28 -64
- package/esm/core/utils/components/VirtualScroll.d.ts +35 -1
- package/esm/core/utils/components/VirtualScroll.js +157 -25
- package/package.json +1 -1
|
@@ -16,6 +16,10 @@ export declare type VirtualScrollProps = {
|
|
|
16
16
|
* @default 10
|
|
17
17
|
*/
|
|
18
18
|
bufferSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Index of the first element on initial render.
|
|
21
|
+
*/
|
|
22
|
+
scrollToIndex?: number;
|
|
19
23
|
} & React.ComponentPropsWithRef<'div'>;
|
|
20
24
|
/**
|
|
21
25
|
* `VirtualScroll` component is used to render a huge amount of items in the DOM. It renders only the ones which are visible
|
|
@@ -38,5 +42,35 @@ export declare type VirtualScrollProps = {
|
|
|
38
42
|
* />
|
|
39
43
|
* @private
|
|
40
44
|
*/
|
|
41
|
-
export declare const VirtualScroll: React.ForwardRefExoticComponent<Pick<VirtualScrollProps, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "itemsLength" | "itemRenderer" | "bufferSize"> & React.RefAttributes<HTMLDivElement>>;
|
|
45
|
+
export declare const VirtualScroll: React.ForwardRefExoticComponent<Pick<VirtualScrollProps, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "itemsLength" | "itemRenderer" | "bufferSize" | "scrollToIndex"> & React.RefAttributes<HTMLDivElement>>;
|
|
46
|
+
/**
|
|
47
|
+
* `useVirtualization` is used for efficiently rendering only the visible rows from a large list.
|
|
48
|
+
* It returns `outerProps` and `innerProps`, which need to be applied on 2 container elements and `visibleChildren` which is a list of virtualized items.
|
|
49
|
+
* @example
|
|
50
|
+
* const itemRenderer = React.useCallback((index: number) => (
|
|
51
|
+
* <li key={index}>
|
|
52
|
+
* This is my item #{index}
|
|
53
|
+
* </li>
|
|
54
|
+
* ), [])
|
|
55
|
+
*
|
|
56
|
+
* const { outerProps, innerProps, visibleChildren } = useVirtualization({itemsLength: 1000, itemRenderer: itemRenderer});
|
|
57
|
+
* return (
|
|
58
|
+
* <div {...outerProps}>
|
|
59
|
+
* <ul {...innerProps}>
|
|
60
|
+
* {visibleChildren}
|
|
61
|
+
* </ul>
|
|
62
|
+
* </div>
|
|
63
|
+
* );
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
export declare const useVirtualization: (props: VirtualScrollProps) => {
|
|
67
|
+
outerProps: React.HTMLAttributes<HTMLElement>;
|
|
68
|
+
innerProps: {
|
|
69
|
+
readonly style: {
|
|
70
|
+
readonly willChange: "transform";
|
|
71
|
+
};
|
|
72
|
+
readonly ref: (instance: HTMLElement | null) => void;
|
|
73
|
+
};
|
|
74
|
+
visibleChildren: JSX.Element[];
|
|
75
|
+
};
|
|
42
76
|
export default VirtualScroll;
|
|
@@ -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 { mergeRefs } from '../hooks';
|
|
28
29
|
import { useResizeObserver } from '../hooks/useResizeObserver';
|
|
29
30
|
var getScrollableParent = function (element, ownerDocument) {
|
|
30
31
|
if (ownerDocument === void 0) { ownerDocument = document; }
|
|
@@ -46,6 +47,14 @@ var getElementHeight = function (element) {
|
|
|
46
47
|
var _a;
|
|
47
48
|
return (_a = element === null || element === void 0 ? void 0 : element.getBoundingClientRect().height) !== null && _a !== void 0 ? _a : 0;
|
|
48
49
|
};
|
|
50
|
+
var getElementHeightWithMargins = function (element) {
|
|
51
|
+
if (!element) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
var margin = parseFloat(getElementStyle(element, 'margin-top')) +
|
|
55
|
+
parseFloat(getElementStyle(element, 'margin-bottom'));
|
|
56
|
+
return getElementHeight(element) + (isNaN(margin) ? 0 : margin);
|
|
57
|
+
};
|
|
49
58
|
var getNumberOfNodesInHeight = function (childHeight, totalHeight) {
|
|
50
59
|
if (!childHeight) {
|
|
51
60
|
return 0;
|
|
@@ -53,7 +62,10 @@ var getNumberOfNodesInHeight = function (childHeight, totalHeight) {
|
|
|
53
62
|
return Math.floor(totalHeight / childHeight);
|
|
54
63
|
};
|
|
55
64
|
var getTranslateValue = function (childHeight, startIndex) {
|
|
56
|
-
|
|
65
|
+
if (startIndex > 0) {
|
|
66
|
+
return childHeight * startIndex;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
57
69
|
};
|
|
58
70
|
var getVisibleNodeCount = function (childHeight, startIndex, childrenLength, scrollContainer) {
|
|
59
71
|
return Math.min(childrenLength - startIndex, getNumberOfNodesInHeight(childHeight, getElementHeight(scrollContainer)));
|
|
@@ -79,21 +91,55 @@ var getVisibleNodeCount = function (childHeight, startIndex, childrenLength, scr
|
|
|
79
91
|
* />
|
|
80
92
|
* @private
|
|
81
93
|
*/
|
|
82
|
-
export var VirtualScroll = React.forwardRef(function (
|
|
83
|
-
var
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
export var VirtualScroll = React.forwardRef(function (props, ref) {
|
|
95
|
+
var _a = useVirtualization(props), innerProps = _a.innerProps, outerProps = _a.outerProps, visibleChildren = _a.visibleChildren;
|
|
96
|
+
return (React.createElement("div", __assign({}, outerProps, { ref: ref }),
|
|
97
|
+
React.createElement("div", __assign({}, innerProps), visibleChildren)));
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* `useVirtualization` is used for efficiently rendering only the visible rows from a large list.
|
|
101
|
+
* It returns `outerProps` and `innerProps`, which need to be applied on 2 container elements and `visibleChildren` which is a list of virtualized items.
|
|
102
|
+
* @example
|
|
103
|
+
* const itemRenderer = React.useCallback((index: number) => (
|
|
104
|
+
* <li key={index}>
|
|
105
|
+
* This is my item #{index}
|
|
106
|
+
* </li>
|
|
107
|
+
* ), [])
|
|
108
|
+
*
|
|
109
|
+
* const { outerProps, innerProps, visibleChildren } = useVirtualization({itemsLength: 1000, itemRenderer: itemRenderer});
|
|
110
|
+
* return (
|
|
111
|
+
* <div {...outerProps}>
|
|
112
|
+
* <ul {...innerProps}>
|
|
113
|
+
* {visibleChildren}
|
|
114
|
+
* </ul>
|
|
115
|
+
* </div>
|
|
116
|
+
* );
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
export var useVirtualization = function (props) {
|
|
120
|
+
var itemsLength = props.itemsLength, itemRenderer = props.itemRenderer, _a = props.bufferSize, bufferSize = _a === void 0 ? 10 : _a, scrollToIndex = props.scrollToIndex, style = props.style, rest = __rest(props, ["itemsLength", "itemRenderer", "bufferSize", "scrollToIndex", "style"]);
|
|
121
|
+
var _b = React.useState(0), startNode = _b[0], setStartNode = _b[1];
|
|
122
|
+
var _c = React.useState(0), visibleNodeCount = _c[0], setVisibleNodeCount = _c[1];
|
|
86
123
|
var scrollContainer = React.useRef();
|
|
87
124
|
var parentRef = React.useRef(null);
|
|
88
|
-
var childHeight = React.useRef(0);
|
|
125
|
+
var childHeight = React.useRef({ first: 0, middle: 0, last: 0 });
|
|
89
126
|
var onScrollRef = React.useRef();
|
|
90
127
|
// Used only to recalculate on resize
|
|
91
|
-
var
|
|
128
|
+
var _d = React.useState(0), scrollContainerHeight = _d[0], setScrollContainerHeight = _d[1];
|
|
129
|
+
var visibleIndex = React.useRef({ start: 0, end: 0 });
|
|
130
|
+
// Used to mark when scroll container has height (updated by resize observer)
|
|
131
|
+
// because before that calculations are not right
|
|
132
|
+
var _e = React.useState(false), isMounted = _e[0], setIsMounted = _e[1];
|
|
92
133
|
var onResize = React.useCallback(function (_a) {
|
|
93
134
|
var height = _a.height;
|
|
135
|
+
// Initial value returned by resize observer is 0
|
|
136
|
+
// So wait for the next one
|
|
137
|
+
if (height > 0) {
|
|
138
|
+
setIsMounted(true);
|
|
139
|
+
}
|
|
94
140
|
setScrollContainerHeight(height);
|
|
95
141
|
}, []);
|
|
96
|
-
var
|
|
142
|
+
var _f = useResizeObserver(onResize), resizeRef = _f[0], resizeObserver = _f[1];
|
|
97
143
|
// Find scrollable parent
|
|
98
144
|
// Needed only on init
|
|
99
145
|
React.useLayoutEffect(function () {
|
|
@@ -102,6 +148,14 @@ export var VirtualScroll = React.forwardRef(function (_a, ref) {
|
|
|
102
148
|
scrollContainer.current = scrollableParent;
|
|
103
149
|
resizeRef(scrollableParent);
|
|
104
150
|
}, [resizeRef]);
|
|
151
|
+
// Stop watching resize, when virtual scroll is unmounted
|
|
152
|
+
React.useLayoutEffect(function () {
|
|
153
|
+
return function () { return resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect(); };
|
|
154
|
+
}, [resizeObserver]);
|
|
155
|
+
var getScrollableContainer = function () {
|
|
156
|
+
var _a, _b;
|
|
157
|
+
return (_a = scrollContainer.current) !== null && _a !== void 0 ? _a : (_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.scrollingElement;
|
|
158
|
+
};
|
|
105
159
|
var visibleChildren = React.useMemo(function () {
|
|
106
160
|
var arr = [];
|
|
107
161
|
var endIndex = Math.min(itemsLength, startNode + visibleNodeCount + bufferSize * 2);
|
|
@@ -112,27 +166,42 @@ export var VirtualScroll = React.forwardRef(function (_a, ref) {
|
|
|
112
166
|
}, [itemsLength, itemRenderer, bufferSize, startNode, visibleNodeCount]);
|
|
113
167
|
// Get child height when children available
|
|
114
168
|
React.useLayoutEffect(function () {
|
|
169
|
+
var _a, _b, _c, _d, _e, _f;
|
|
115
170
|
if (!parentRef.current || !visibleChildren.length) {
|
|
116
171
|
return;
|
|
117
172
|
}
|
|
118
173
|
var firstChild = parentRef.current.children.item(0);
|
|
119
|
-
|
|
174
|
+
var secondChild = parentRef.current.children.item(1);
|
|
175
|
+
var lastChild = parentRef.current.children.item(parentRef.current.children.length - 1);
|
|
176
|
+
var firstChildHeight = Number((_b = (_a = getElementHeightWithMargins(firstChild)) === null || _a === void 0 ? void 0 : _a.toFixed(2)) !== null && _b !== void 0 ? _b : 0);
|
|
177
|
+
childHeight.current = {
|
|
178
|
+
first: firstChildHeight,
|
|
179
|
+
middle: Number((_d = (_c = getElementHeightWithMargins(secondChild)) === null || _c === void 0 ? void 0 : _c.toFixed(2)) !== null && _d !== void 0 ? _d : firstChildHeight),
|
|
180
|
+
last: Number((_f = (_e = getElementHeightWithMargins(lastChild)) === null || _e === void 0 ? void 0 : _e.toFixed(2)) !== null && _f !== void 0 ? _f : firstChildHeight),
|
|
181
|
+
};
|
|
120
182
|
}, [visibleChildren.length]);
|
|
121
183
|
var updateVirtualScroll = React.useCallback(function () {
|
|
122
|
-
var
|
|
123
|
-
var scrollableContainer = (_a = scrollContainer.current) !== null && _a !== void 0 ? _a : (_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.scrollingElement;
|
|
184
|
+
var scrollableContainer = getScrollableContainer();
|
|
124
185
|
if (!scrollableContainer) {
|
|
125
186
|
return;
|
|
126
187
|
}
|
|
127
|
-
var start = getNumberOfNodesInHeight(childHeight.current, scrollableContainer.scrollTop);
|
|
128
|
-
var
|
|
188
|
+
var start = getNumberOfNodesInHeight(childHeight.current.middle, Math.round(scrollableContainer.scrollTop));
|
|
189
|
+
var visibleNodes = getVisibleNodeCount(childHeight.current.middle, start, itemsLength, scrollableContainer);
|
|
190
|
+
// If there are less items at the end than buffer size
|
|
191
|
+
// show more items at the start.
|
|
192
|
+
// Have boundaries for edge cases, e.g. 1 item length
|
|
193
|
+
var startIndex = Math.min(Math.max(0, start - bufferSize), Math.max(0, itemsLength - bufferSize * 2 - visibleNodes));
|
|
194
|
+
visibleIndex.current = { start: start, end: start + visibleNodes };
|
|
129
195
|
setStartNode(startIndex);
|
|
130
|
-
setVisibleNodeCount(
|
|
196
|
+
setVisibleNodeCount(visibleNodes);
|
|
131
197
|
if (!parentRef.current) {
|
|
132
198
|
return;
|
|
133
199
|
}
|
|
134
|
-
parentRef.current.style.transform = "translateY(".concat(getTranslateValue(childHeight.current, startIndex), "px)");
|
|
200
|
+
parentRef.current.style.transform = "translateY(".concat(getTranslateValue(childHeight.current.middle, startIndex), "px)");
|
|
135
201
|
}, [bufferSize, itemsLength]);
|
|
202
|
+
var onScroll = React.useCallback(function () {
|
|
203
|
+
updateVirtualScroll();
|
|
204
|
+
}, [updateVirtualScroll]);
|
|
136
205
|
var removeScrollListener = React.useCallback(function () {
|
|
137
206
|
var _a, _b;
|
|
138
207
|
if (!onScrollRef.current) {
|
|
@@ -147,22 +216,85 @@ export var VirtualScroll = React.forwardRef(function (_a, ref) {
|
|
|
147
216
|
React.useLayoutEffect(function () {
|
|
148
217
|
var _a, _b;
|
|
149
218
|
removeScrollListener();
|
|
150
|
-
onScrollRef.current =
|
|
219
|
+
onScrollRef.current = onScroll;
|
|
151
220
|
if (!scrollContainer.current ||
|
|
152
221
|
scrollContainer.current === ((_a = parentRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.body)) {
|
|
153
|
-
(_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.addEventListener('scroll',
|
|
222
|
+
(_b = parentRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.addEventListener('scroll', onScroll);
|
|
154
223
|
}
|
|
155
224
|
else {
|
|
156
|
-
scrollContainer.current.addEventListener('scroll',
|
|
225
|
+
scrollContainer.current.addEventListener('scroll', onScroll);
|
|
157
226
|
}
|
|
158
227
|
return removeScrollListener;
|
|
159
|
-
}, [
|
|
228
|
+
}, [onScroll, removeScrollListener]);
|
|
229
|
+
React.useLayoutEffect(function () {
|
|
230
|
+
if (!isMounted) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
var scrollableContainer = getScrollableContainer();
|
|
234
|
+
if (!scrollableContainer || scrollToIndex == null) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// if `scrollToIndex` is not visible, scroll to it
|
|
238
|
+
if (scrollToIndex > visibleIndex.current.end ||
|
|
239
|
+
scrollToIndex < visibleIndex.current.start) {
|
|
240
|
+
var indexDiff = scrollToIndex > visibleIndex.current.end
|
|
241
|
+
? scrollToIndex - visibleIndex.current.end
|
|
242
|
+
: scrollToIndex - visibleIndex.current.start;
|
|
243
|
+
if (scrollToIndex === 0) {
|
|
244
|
+
scrollableContainer.scrollTo({ top: 0 });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// If go down: add to the existing scrollTop needed height
|
|
248
|
+
// If go up: calculate the exact scroll top
|
|
249
|
+
scrollableContainer.scrollTo({
|
|
250
|
+
top: indexDiff > 0
|
|
251
|
+
? Math.ceil(scrollableContainer.scrollTop) +
|
|
252
|
+
indexDiff * childHeight.current.middle
|
|
253
|
+
: scrollToIndex * childHeight.current.middle,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// if `scrollToIndex` is the first visible node
|
|
257
|
+
// ensure it is fully visible
|
|
258
|
+
if (scrollToIndex === visibleIndex.current.start) {
|
|
259
|
+
var roundedScrollTop = Math.round(scrollableContainer.scrollTop);
|
|
260
|
+
var diff = roundedScrollTop % childHeight.current.middle;
|
|
261
|
+
diff > 0 &&
|
|
262
|
+
scrollableContainer.scrollTo({
|
|
263
|
+
top: roundedScrollTop - diff,
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// if `scrollToIndex` is the last visible node
|
|
268
|
+
// ensure it is fully visible
|
|
269
|
+
if (scrollToIndex === visibleIndex.current.end) {
|
|
270
|
+
var diff = (scrollableContainer.offsetHeight - childHeight.current.first) %
|
|
271
|
+
childHeight.current.middle;
|
|
272
|
+
var roundedScrollTop = Math.ceil(scrollableContainer.scrollTop);
|
|
273
|
+
var scrollTopMod = roundedScrollTop % childHeight.current.middle;
|
|
274
|
+
if (diff > 0 && scrollTopMod === 0) {
|
|
275
|
+
scrollableContainer.scrollTo({
|
|
276
|
+
top: roundedScrollTop + childHeight.current.middle - diff,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}, [scrollToIndex, isMounted]);
|
|
160
281
|
React.useLayoutEffect(function () {
|
|
282
|
+
if (!scrollContainerHeight) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
161
285
|
updateVirtualScroll();
|
|
162
|
-
}, [scrollContainerHeight,
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
286
|
+
}, [scrollContainerHeight, updateVirtualScroll]);
|
|
287
|
+
return {
|
|
288
|
+
outerProps: __assign({ style: __assign({ overflow: 'hidden', minHeight: itemsLength > 1
|
|
289
|
+
? Math.max(itemsLength - 2, 0) * childHeight.current.middle +
|
|
290
|
+
childHeight.current.first +
|
|
291
|
+
childHeight.current.last
|
|
292
|
+
: childHeight.current.middle, width: '100%' }, style) }, rest),
|
|
293
|
+
innerProps: {
|
|
294
|
+
style: { willChange: 'transform' },
|
|
295
|
+
ref: mergeRefs(parentRef), // convert object ref to callback ref for better types
|
|
296
|
+
},
|
|
297
|
+
visibleChildren: visibleChildren,
|
|
298
|
+
};
|
|
299
|
+
};
|
|
168
300
|
export default VirtualScroll;
|